diff --git a/.gitattributes b/.gitattributes index 200b5e5..2b54665 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ *.svg text eol=lf *.png binary *.gif binary +*.woff2 binary diff --git a/.gitignore b/.gitignore index 65da810..4aaa827 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ evals/results/ !CLAUDE.md !.claude-plugin/ !.claude-plugin/** + +# The enterprise example's local registry keeps its packages here +examples/enterprise/registry/storage/ diff --git a/ROADMAP.md b/ROADMAP.md index e767a6d..e594a7f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -56,7 +56,16 @@ Everything else gets ordinary care, but none of these three gets traded away for - **Keep the version `ng update` picks inside a peer range (2026-11-08).** For a shared package the plan names the smallest move, while `ng update` takes the newest release the peers allow, such as TypeScript 5.9 where the plan said 5.8. The plan now prints the range, and anything inside it stays. *Why:* going back to the plan's pick fights `ng update` and costs a reinstall, and on readme-wizard an agent installed a package the re-run plan no longer listed. *Gave up:* the smallest possible move, for shared packages only. A library `ng update` takes a major further than the plan is reported, and the plan checks the license of the newest release that fits as well as its own pick. - **M7's internal libraries live in their own repo and reach the app through a private registry (2026-11-08).** A local Verdaccio on the Windows host stands in for a company's Artifactory or GitHub Packages. *Why:* "upgrade the libraries first" only means something when they're published separately, and that's how teams with shared UI libraries usually work. *Gave up:* the monorepo case, where `ng update` moves the libraries and the app in one go. - **M7's video is built with HyperFrames from the real run (2026-11-08).** Its terminal output, diffs and screenshots go into HTML scenes rendered to video, with a synthetic voice, captions and chapters, about 8 to 10 minutes, and short asides for newer and more senior developers. *Why:* it shows what really happened, and a scene can be fixed and rendered again without re-recording. *Gave up:* a live screen recording's feel. +- **A scope with a registry of its own marks a library as the team's (2026-11-08).** *Why:* that's how a company publishes its packages (`@acme:registry=...`), and npm's own config already says it, so the plan keeps no list. *Gave up:* a company that serves everything, its own packages too, from one default registry gets no such hint. (Superseded by the 2026-11-08 decision on what the npm config shows.) +- **The skill raises a Node pin the repo carries (2026-11-08).** A dev container, `.nvmrc`, `engines` field or CI workflow below the new Angular's range is raised in that major's commit. *Why:* those are the team's machines, and the upgrade would pass here and fail there. M7's dev container pins Node 22.12, which Angular 22 refuses. *Gave up:* a change outside the app's code that nobody asked for, so it's named under hand fixes. A pin outside the app's folder is raised only when it builds, tests or runs this app, since this app's checks can't catch what the change does to the rest of the repo. The plan reads only the Node major in a dev container's tag, so an image whose tag freezes an older Node, such as `typescript-node:1-22-bookworm` with Node 22.16.0, passes. +- **The enterprise example lives in `examples/enterprise/`, as templates a setup script copies out (2026-11-08).** *Why:* the tutorial needs two repositories and a registry, which can't sit as git repositories inside this one, and anyone can rerun it from a clone. *Gave up:* the evals don't run it yet. A case could, with the registry started in CI. +- **The video's source is in the repo, and the video isn't (2026-11-08).** `docs/video/enterprise/` keeps the narration, the scenes and the build scripts, and leaves out the voice, the timings and the render, which they make again. *Why:* being able to fix a line or a scene and render again is why it's built this way, and an eight-minute video would weigh down every clone. *Gave up:* making it again needs FFmpeg and a local Kokoro voice, and a render loads GSAP from a CDN. - **No unit test running stops the run, like a failing build (2026-11-08).** *Why:* on readme-wizard, one spec that didn't compile stopped Karma before any of its 20 specs ran, at the start and after all four majors, and the report called it one failing test. *Gave up:* one more question, on apps whose tests were already broken. +- **The plan says what the npm config shows, and the skill confirms who owns the library (2026-11-08).** A blocker whose scope has a registry of its own gets a note naming that registry, and an offer to upgrade it first if it's the team's. *Why:* a licensed vendor sets up its registry the same way, as Font Awesome Pro does for `@fortawesome` and Infragistics for `@infragistics`, so the config shows the registry, not the owner. *Gave up:* the plan can't tell the team's library from a vendor's, so the skill checks the package's repository and author, or asks. +- **Read a pnpm workspace's registry settings at its root (2026-11-08).** The plan reads the root's `.npmrc`, lays over it only the keys the app's own `.npmrc` sets, and tries `npm view` from the root's folder, then the app's, when a read needs a login. *Why:* pnpm reads the root's `.npmrc`, so a plan that looked only in the app's folder missed the team's registry and called a blocked app reachable. *Gave up:* pnpm 12 ignores the app's `.npmrc`, but the plan still lays its keys on top, as pnpm 10 and older do. An app inside an npm, yarn or bun workspace still gets no plan, because npm refuses to list its config there. And `ng update` from Angular CLI 22.0 and older reads only the app folder's `.npmrc`, so the troubleshooting guide shows how to give it the team's registry for that hop. +- **When a library's release is too young for `ng update`, the runner installs it first (2026-11-08).** `update.mjs --install-first` starts from a clean tree, adds those libraries the way `package.json` lists them, checks that only `package.json` and the lockfile changed, then runs `ng update` for the rest with `--allow-dirty`. *Why:* from Angular CLI 22.1, `ng update` skips a release younger than the package manager's minimum release age and reads no exemption list, and the runner refuses `--allow-dirty` and a dirty tree on purpose. Doing the add in the runner also avoids cmd.exe dropping the `^` from a typed `pnpm add pkg@^22.0.0`. *Gave up:* an exemption the person approves needs a commit of its own first, and a library that ships migrations has to wait the day out, because installing it first skips them. +- **The example's dev container pins corepack 0.34.7 (2026-11-08).** *Why:* the latest, 0.36.0, declares the image's Node 22.12 unsupported, and the image changed with every corepack release. 0.34.7 is the last whose engines include Node 22.12, and it starts pnpm 12.16.1. *Gave up:* newer corepack releases, until the image's Node is raised. +- **A Node in a gap of Angular's range counts as too old (2026-11-08).** The plan calls a Node `unsupported` only when it's above every version in the range. One below the range, or in a gap of it such as 24.14 in Angular 22's `^22.22.3 || ^24.15.0 || >=26.0.0`, is `too-old`. *Why:* `ng` refuses to start on a Node in a gap, and the skill reads `unsupported` as carry on. *Gave up:* Node 23 and 25 sit in the gaps too, so they count as too old, though Angular CLI 22 only warns on them and runs. ## Shipped @@ -103,14 +112,32 @@ Met on 2026-11-08. With the skill: yarn 16/16, bun 16/16 and the catalog 14/14, A company's app is rarely one package. This release takes one built like the real thing from Angular 21 to 22, and turns the run into a tutorial. The video is for developers who are new to agentic coding but not to development, and should still work for juniors and seniors. -- [ ] A fixture: an Angular 21 app on the pnpm toolchain, with internal UI libraries whose peer ranges stop at Angular 21, so they have to reach 22 before the app can. -- [ ] Set up for a dev container, and upgraded from the Windows host, not inside the container. -- [ ] When a blocking library belongs to the person, offer to upgrade it first, in dependency order, besides waiting, replacing or forcing. -- [ ] Take it to Angular 22 with the skill: the libraries first, then the app. -- [ ] Make a video tutorial from that run. +- [x] A fixture: an Angular 21 app on the pnpm toolchain, with internal UI libraries whose peer ranges stop at Angular 21, so they have to reach 22 before the app can. It's `examples/enterprise/`: `acme-ui`, `acme-portal` and a local registry. +- [x] Set up for a dev container, and upgraded from the Windows host, not inside the container. Rebuilt afterwards, the container runs Node 22.23.4, builds the app and serves it. +- [x] When a blocking library belongs to the person, offer to upgrade it first, in dependency order, besides waiting, replacing or forcing. +- [x] Take it to Angular 22 with the skill: the libraries first, then the app. Claude Code did it in six turns from the Windows host, for $7.62. What it ran into, and what the skill learned from it: + - `ng update` refused at first, because the libraries' new 22.0.0 was minutes old. The agent blamed pnpm 12's minimum release age, and Claude Code's own safety check stopped it from relaxing that, so it asked. A later check showed pnpm installs a release named exactly and writes the exemption itself, unless `minimumReleaseAgeStrict` is set. + - Exempted, the release still failed: from Angular CLI 22.1, `ng update` applies the age rule in its own resolver and reads no exemption list. The agent found that in the CLI's source, then installed the releases by hand before updating Angular, with approval. `update.mjs --install-first` does that step now, and the troubleshooting guide gives the cause and both ways on. + - The agent missed the dev container's `FROM node:22.12.0`, and the container then refused Angular 22. The plan now names the repo's own Node pins below a major's range: from `.nvmrc`, `.node-version`, `.tool-versions`, a Dockerfile, the dev container and `package.json`'s `engines` and `volta`, in the app's folder and at the repo's root, and from GitHub workflows. The skill searches for the rest. +- [x] Make a video tutorial from that run. It runs 7:52 in eight chapters, with captions and asides for newer developers and for team leads. Its source is in `docs/video/enterprise/`. **Done when:** the skill takes the libraries and then the app to Angular 22 from the Windows host, the dev container still builds and serves the app afterwards, and the video is made. +Met on 2026-11-08. A review of the whole milestone afterwards found real problems, among them a pnpm workspace's `.npmrc` that the plan never read, so a blocked app looked reachable, and a way past the release-age rule that the runner refused to carry out. They're fixed, and what the review left for later is in M8. + +### M8: 0.5.0, the edges the enterprise run found + +M7's run and its review found places where the plan says too little, or can't run at all. + +- [ ] The plan checks release age the way the package manager and `ng update` do. A release younger than pnpm's `minimum-release-age`, yarn's `npmMinimalAgeGate` or npm's `before` makes the printed command fail, so the plan should name it and the wait instead of calling the hop ready. +- [ ] A plan for an app inside an npm, yarn or bun workspace. npm refuses `config list` there with `ENOWORKSPACES`, so the plan exits today. It needs the workspace's root, lockfile and package manager. `npm prefix` can't find the root, because it masks path parts that look like secrets. Check too whether `ng update` from CLI 22.0 and older misses a scope registry set at such a root, as it does at a pnpm one. +- [ ] Judge Node for Angular 22 and newer the way its CLI does, before Node 27 is stable. CLI 22 starts only on the majors its range lists, and warns on 23 and 25, so on Node 27 `ng` exits while the plan says ok. +- [ ] Name a Node pin the plan can't read, such as `lts/iron` in `.nvmrc`, instead of counting it as fitting. Read a dev container at the repo's root (`.devcontainer.json`), and a Dockerfile that `build.dockerfile` names elsewhere. +- [ ] Run the example's dev container on Docker Engine on Linux, which has no `host.docker.internal`: add the host gateway, and have the registry listen on the bridge address too, never on every address. Check it on a Linux host first. +- [ ] Run the enterprise example as an eval case, with its registry started in CI, so a change that breaks it shows up. + +**Done when:** the plan names a release too young for the printed command, an app in an npm, yarn or bun workspace gets a plan, and the enterprise example runs in the evals. + ## Later - Run the skill for real in GitHub Copilot. So far it's checked from Copilot's docs only. diff --git a/docs/video/enterprise/.gitignore b/docs/video/enterprise/.gitignore new file mode 100644 index 0000000..53eb517 --- /dev/null +++ b/docs/video/enterprise/.gitignore @@ -0,0 +1,10 @@ +# Made from the sources here by tools/voice.mjs, tools/build.mjs and the HyperFrames CLI +.voice/ +.hyperframes/ +assets/vo-*.wav +timings.json +index.html +compositions/ +chapters.txt +meta.json +*.mp4 diff --git a/docs/video/enterprise/README.md b/docs/video/enterprise/README.md new file mode 100644 index 0000000..3134737 --- /dev/null +++ b/docs/video/enterprise/README.md @@ -0,0 +1,28 @@ +# The enterprise upgrade, on video + +The source of M7's tutorial, where Claude Code with ng-hop takes [`examples/enterprise`](../../../examples/enterprise/) from Angular 21 to 22, the team's own libraries first. It's made with [HyperFrames](https://hyperframes.heygen.com) from the real run: the terminal lines, diffs and screenshots come from what the run did, and the narration is a synthetic voice. + +`script.mjs` holds the narration, a sentence at a time, and the styles every chapter shares. `scenes/.html` holds a chapter's visuals, where `data-cue="s12"` brings an element in as sentence `s12` starts and `data-out` takes it away. + +## Make it + +It needs Node 22 or newer and FFmpeg. The voice also needs a Python with `kokoro-onnx` and `soundfile`, named in `HYPERFRAMES_PYTHON`. + +```sh +node tools/voice.mjs # the narration, and timings.json from it +node tools/build.mjs # index.html and compositions/, from the script, the scenes and the timings +npx --yes hyperframes@0.9.22 check +npx --yes hyperframes@0.9.22 render --quality delivery --output enterprise.mp4 +``` + +After a scene changes, build, check and render again. After the narration changes, make the voice first, since every cue moves with it. The build also writes `chapters.txt`, the chapter list for the video's description. + +JetBrains Mono is under the SIL Open Font License, in `assets/fonts/LICENSE`. + +## What changed after the run + +The video shows ng-hop as it was during the run. A review afterwards changed three things a viewer may notice: + +- The plan no longer says a library "comes from your own registry". It names the registry the npm config sets for that scope, because a vendor's paid registry is set up the same way, and the skill checks who owns the library. +- In "Publish, and a safeguard", the agent blames pnpm's minimum release age. pnpm in fact installs a release named exactly and writes the exemption itself. The cause was the age filter in `ng update`, which the next chapter finds. +- The agent no longer has to find that filter itself. The troubleshooting guide names it, and `update.mjs --install-first` installs the young releases before updating Angular, the step the agent did by hand. diff --git a/docs/video/enterprise/assets/fonts/LICENSE b/docs/video/enterprise/assets/fonts/LICENSE new file mode 100644 index 0000000..8f7ed67 --- /dev/null +++ b/docs/video/enterprise/assets/fonts/LICENSE @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/video/enterprise/assets/fonts/jetbrains-mono-latin-400-normal.woff2 b/docs/video/enterprise/assets/fonts/jetbrains-mono-latin-400-normal.woff2 new file mode 100644 index 0000000..5858873 Binary files /dev/null and b/docs/video/enterprise/assets/fonts/jetbrains-mono-latin-400-normal.woff2 differ diff --git a/docs/video/enterprise/assets/fonts/jetbrains-mono-latin-700-normal.woff2 b/docs/video/enterprise/assets/fonts/jetbrains-mono-latin-700-normal.woff2 new file mode 100644 index 0000000..3a4e333 Binary files /dev/null and b/docs/video/enterprise/assets/fonts/jetbrains-mono-latin-700-normal.woff2 differ diff --git a/docs/video/enterprise/assets/lockup-dark.svg b/docs/video/enterprise/assets/lockup-dark.svg new file mode 100644 index 0000000..a65d68c --- /dev/null +++ b/docs/video/enterprise/assets/lockup-dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/video/enterprise/assets/mark.svg b/docs/video/enterprise/assets/mark.svg new file mode 100644 index 0000000..47ff042 --- /dev/null +++ b/docs/video/enterprise/assets/mark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/video/enterprise/assets/portal-angular-21.png b/docs/video/enterprise/assets/portal-angular-21.png new file mode 100644 index 0000000..5d8c3e0 Binary files /dev/null and b/docs/video/enterprise/assets/portal-angular-21.png differ diff --git a/docs/video/enterprise/assets/portal-angular-22.png b/docs/video/enterprise/assets/portal-angular-22.png new file mode 100644 index 0000000..5d8c3e0 Binary files /dev/null and b/docs/video/enterprise/assets/portal-angular-22.png differ diff --git a/docs/video/enterprise/hyperframes.json b/docs/video/enterprise/hyperframes.json new file mode 100644 index 0000000..dc9ec2e --- /dev/null +++ b/docs/video/enterprise/hyperframes.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/hyperframes.json", + "registry": "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry", + "paths": { + "blocks": "compositions", + "components": "compositions/components", + "assets": "assets" + }, + "media": { + "autoProxy": true + } +} diff --git a/docs/video/enterprise/package.json b/docs/video/enterprise/package.json new file mode 100644 index 0000000..18ccf11 --- /dev/null +++ b/docs/video/enterprise/package.json @@ -0,0 +1,10 @@ +{ + "name": "ng-hop-enterprise-video", + "private": true, + "type": "module", + "scripts": { + "dev": "npx --yes hyperframes@0.9.22 preview", + "check": "npx --yes hyperframes@0.9.22 check", + "render": "npx --yes hyperframes@0.9.22 render" + } +} diff --git a/docs/video/enterprise/scenes/age.html b/docs/video/enterprise/scenes/age.html new file mode 100644 index 0000000..09e457b --- /dev/null +++ b/docs/video/enterprise/scenes/age.html @@ -0,0 +1,96 @@ +
+
acme-portal
+
+
Yes, exempt the two versions like we did for 21.4.0, commit that, …
+
pnpm exec node …/update.mjs @angular/core@22 …
+
Package "@acme/ui-core" … (requires "^21.2.0", would install "22.2.5").
+
Incompatible peer dependencies found. …
+
+
+
+
commit 792cc47
+
+
pnpm-workspace.yaml
+
minimumReleaseAgeExclude:
+
- '@acme/ui-core@21.4.0'
+
- '@acme/ui-forms@21.4.0'
+
+ - '@acme/ui-core@22.0.0'
+
+ - '@acme/ui-forms@22.0.0'
+
+
+ +
+
@angular/cli 22.2.1 · update-resolver.js
+
+
… sed -n 100,135p "$CLI/commands/update/update-resolver.js"
+
function isReleaseAgeSatisfied(registryClient, metadata, version) {
+
const minReleaseAge = registryClient.minReleaseAge;
+
…
+
return Date.now() - publishTime >= minReleaseAge;
+
}
+
+
+
+

Why it still fails

+

It reads pnpm's minimum-release-age setting. When that's unset, which it is here, it assumes pnpm 11+'s default of 24 hours.

+

It never reads minimumReleaseAgeExclude.

+
+ +
+

Option 1

+

Wait until about 08:30 UTC tomorrow (9 Nov), when 22.0.0 is 24 hours old, then rerun the same command.

+
+
+

Option 2

+

Install the libraries first, then update Angular.

+
+
pnpm add @acme/ui-core@22.0.0 @acme/ui-forms@22.0.0
+
ng update @angular/core@22 @angular/cli@22
+
+
+
+

The catch

+

ng update won't start with uncommitted changes, so the second step needs --allow-dirty, which is a departure from the planned command.

+
+
+

Shall I go ahead with option 2, or wait until tomorrow?

+
+ +
+
acme-portal
+
+
Go with option 2.
+
pnpm add @acme/ui-core@22.0.0 @acme/ui-forms@22.0.0
+
+ @acme/ui-core 22.0.0
+
+ @acme/ui-forms 22.0.0
+
… pnpm dlx @angular/cli@22 update @angular/core@22 @angular/cli@22 --allow-dirty
+
Updating package.json with dependency @angular/core to version 22.2.5...
+
** Executing migrations of package '@angular/core' **
+
** Executing migrations of package '@angular/cli' **
+
+
+ +
+
acme-portal
+
+
npx --no-install ng build
+
Application bundle generation complete. [5.263 seconds] …
+
npx --no-install ng test --watch=false
+
Tests 1 passed (1)
+
node …/smoke.mjs
+
App check: PASS
+
browser: chrome, rendered content
+
+
+
Acme Portal on Angular 22
+
Angular 22
+ +
+

Done by hand in the portal

+

Install order. I installed the two Acme releases with pnpm add first, then ran ng update … --allow-dirty.

+

This was needed because Angular CLI 22's ng update applies pnpm's default 24-hour release age itself and ignores minimumReleaseAgeExclude.

+
+
+

Not checked

+

The smoke check … didn't fill in or submit the request form. … it's worth trying by hand.

+
diff --git a/docs/video/enterprise/scenes/ask.html b/docs/video/enterprise/scenes/ask.html new file mode 100644 index 0000000..531caec --- /dev/null +++ b/docs/video/enterprise/scenes/ask.html @@ -0,0 +1,45 @@ +
+
acme-portal
+
+
Upgrade this app to Angular 22.
+
Launching skill: ng-hop:ng-hop
+
node …/ng-hop/scripts/plan.mjs --to 22
+
Angular 21.2.26 (from lockfile), pnpm, Node 24.20.0. Latest Angular: 22.2.5.
+
Result: BLOCKED. Angular 22 isn't possible today, so this project stays on 21 …
+
Note: @acme/ui-core and @acme/ui-forms come from your own registry.
+
If they are your team's, each can be upgraded first, with a release that accepts Angular 22,
+
in this order: @acme/ui-core, then @acme/ui-forms, which needs @acme/ui-core.
+
Blockers
+
Angular 22: @acme/ui-core: no stable release at or above 21.4.0 supports Angular 22;
+
the latest, 21.4.0 (2026-11-08), requires ^21.2.0; it comes from your own registry, …
+
Angular 22: @acme/ui-forms: no stable release at or above 21.4.0 supports Angular 22;
+
the latest, 21.4.0 (2026-11-08), requires ^21.2.0; it comes from your own registry, …
+
+
+ +
+

The agent's answer

+

I haven't changed anything in either repo and didn't create a branch.

+

What I recommend: upgrade acme-ui first. Waiting would only mean waiting on your own team.

+ +

Shall I start on the Angular 22 upgrade in acme-ui?

+
+ +
+

Newer to this?

+

^21.2.0 is a peer range: the Angular versions a library says it works with. Forcing past it is how upgrades break at runtime.

+

It gave you the choice instead: "The other options are to force acme-portal past the peer ranges with an override, which is untested, or to stay on 21 for now."

+
+ +
+

Leading a team?

+

A person makes this call. The agent brings the facts:

+ +
diff --git a/docs/video/enterprise/scenes/container.html b/docs/video/enterprise/scenes/container.html new file mode 100644 index 0000000..06f7c59 --- /dev/null +++ b/docs/video/enterprise/scenes/container.html @@ -0,0 +1,75 @@ +
+

The upgrade report

+

Node: Angular 22 needs Node ^22.22.3 || ^24.15.0 || >=26.

+

Neither repo pins a Node version, but CI and build images … will fail.

+
+ +
+
acme-portal dev container · ng build
+
+
Node.js version v22.12.0 detected.
+
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.
+
[ELIFECYCLE] Command failed with exit code 3.
+
+
+ +
+
Claude Code · acme-portal
+
+
I opened the dev container and ng build fails there: 'Node.js version v22.12.0 detected. …' Can you fix it?
+
This was my mistake. I checked acme-ui for Node pins … but never checked acme-portal.
+
+
+ +
+
acme-portal · commit 62634e2
+
+
build(devcontainer): move Node 22.12.0 to 22.23.4 for Angular 22
+
.devcontainer/Dockerfile
+
-FROM node:22.12.0-bookworm
+
+FROM node:22.23.4-bookworm
+
+
+ +
Node 22.23.4The newest Node 22 LTS release. It keeps your team on Node 22 and is within Angular 22's range.
+ +
+
Claude Code · acme-portal
+
+
docker build -q -t acme-portal-devcheck .devcontainer
+
docker run --rm … acme-portal-devcheck bash -c '…'
+
v22.23.4
+
build exit 0
+
test exit 0
+
+
+ +
+
dev container, rebuilt
+
+
node v22.23.4, pnpm 12.16.1
+
build exit 0
+
serve: 200 page has <app-root>
+
+
+ +
ng-hop
+
Its plan now finds Node pins itself
+ +
DockerfilesThe image in each FROM line
+
Dev containersdevcontainer.json's image or Node feature
+
Version files.nvmrc, .node-version, .tool-versions
+
CI workflowsnode-version in .github/workflows
+ +
+
ng-hop plan · acme-portal
+
+
Node pins below what Angular 22 needs (^22.22.3 || ^24.15.0 || >=26.0.0), in the repo: .devcontainer/Dockerfile (node:22.12.0-bookworm)
+
+
+ +
+

Not the same Node

+

The agent ran on the Windows host, on Node 24.20.0.

+

The team builds in the dev container, on the Node its Dockerfile pins.

+
diff --git a/docs/video/enterprise/scenes/intro.html b/docs/video/enterprise/scenes/intro.html new file mode 100644 index 0000000..fb8c12a --- /dev/null +++ b/docs/video/enterprise/scenes/intro.html @@ -0,0 +1,27 @@ +
+
An enterprise Angular upgrade, with an agent
+
Angular 21 to 22, with the team's own libraries first
+
+
Acme Portal on Angular 21
+
Angular 21
+ +
+

The catch

+

The portal is built on @acme/ui-core and @acme/ui-forms, and they only accept Angular 21.

+
+ +
+
1The libraries move to Angular 22
+
2They're published
+
3Then the app follows
+
+ +
Real problems, handled in the open
+ +
+

Watch where it stops

+ +
diff --git a/docs/video/enterprise/scenes/libraries.html b/docs/video/enterprise/scenes/libraries.html new file mode 100644 index 0000000..d6cd836 --- /dev/null +++ b/docs/video/enterprise/scenes/libraries.html @@ -0,0 +1,110 @@ +
+
Claude Code
+
+
Yes, start with acme-ui.
+
… cd /c/code/acme-demo/acme-ui && …
+
git switch -c upgrade/angular-22 && … pnpm build …; pnpm test …
+
Switched to a new branch 'upgrade/angular-22'
+
Baseline: build passes, tests pass (1 + 1).
+
node …/guide.mjs --from 21 --to 22; …
+
Angular 21 to 22, from the official Update Guide …
+
…update.mjs @angular/core@22 @angular/cli@22 ng-packagr@22.2.6
+
Done in 2m 39.9s using pnpm v12.16.1
+
+
+ +
+
1A branch
+
2A starting point: both build, tests pass
+
3Angular's update guide, 21 → 22
+
4ng update for Angular, the CLI and ng-packagr, together
+
+ +
By hand: what ng update doesn't do for a library
+ +
+
projects/acme/ui-core/package.json
+
+
"name": "@acme/ui-core",
+
- "version": "21.4.0",
+
+ "version": "22.0.0",
+
"peerDependencies": {
+
- "@angular/common": "^21.2.0",
+
- "@angular/core": "^21.2.0"
+
+ "@angular/common": "^22.0.0",
+
+ "@angular/core": "^22.0.0"
+
+
+ +
+
projects/acme/ui-forms/package.json
+
+
"name": "@acme/ui-forms",
+
- "version": "21.4.0",
+
+ "version": "22.0.0",
+
"peerDependencies": {
+
- "@angular/common": "^21.2.0",
+
- "@angular/core": "^21.2.0",
+
- "@angular/forms": "^21.2.0",
+
- "@acme/ui-core": "^21.4.0"
+
+ "@angular/common": "^22.0.0",
+
+ "@angular/core": "^22.0.0",
+
+ "@angular/forms": "^22.0.0",
+
+ "@acme/ui-core": "^22.0.0"
+
+
+ +
+
+
✔ Built @acme/ui-core
+
✔ Built @acme/ui-forms
+
Tests 1 passed (1)
+
a76bb41 chore(deps): upgrade to Angular 22
+
+
+ +
+

What I changed by hand

+

(the parts a reviewer should read)

+ +
+ +
+

What ng update changed

+

Angular 22 makes components without a change-detection setting update less often by default (OnPush). The migration added ChangeDetectionStrategy.Eager to every component and test host so they keep the old behaviour.

+
+ +
+
projects/acme/ui-core/src/lib/button.ts
+
+
@Component({
+
selector: 'acme-button',
+
+ changeDetection: ChangeDetectionStrategy.Eager,
+
+
+ +
+
ng-packagr/CHANGELOG.md
+
+
## 22.2.0-next.1 (2026-07-29)
+
### Features
+
* ng-packagr: replace rollup with rolldown
+
+
+ +
+

Library changes

+

I read ng-packagr 22's changelog … from 22.2 it bundles with Rolldown instead of Rollup.

+

The libraries still build, but the published output comes from a different bundler, so it's worth a look once acme-portal uses it.

+
+ +
+

Ready to publish

+

to http://localhost:4873/ (set in .npmrc), in this order:

+
1@acme/ui-core@22.0.0
+
2@acme/ui-forms@22.0.0
+

Shall I publish both packages now?

+
diff --git a/docs/video/enterprise/scenes/publish.html b/docs/video/enterprise/scenes/publish.html new file mode 100644 index 0000000..cd52693 --- /dev/null +++ b/docs/video/enterprise/scenes/publish.html @@ -0,0 +1,88 @@ +
+
Claude Code · acme-portal
+
+
Yes, publish both, then upgrade the portal.
+
cd …/acme-ui && … pnpm build … && pnpm run publish …
+
Published package @acme/ui-core@22.0.0
+
Published package @acme/ui-forms@22.0.0
+
cd …/acme-portal && … node …/plan.mjs --to 22
+
Result: Angular 22 is reachable.
+
@acme/ui-core: 21.4.0 -> 22.0.0; supports it (^22.0.0)
+
@acme/ui-forms: 21.4.0 -> 22.0.0; supports it (^22.0.0)
+
+
+ +
Acme Portal on Angular 21, running in Chrome
+ +
+

Before changing anything

+ +
+ +
+
Claude Code · acme-portal
+
+
pnpm exec node …/update.mjs @angular/core@22 @angular/cli@22 @acme/ui-core@22.0.0 …
+
Package "@acme/ui-core" has an incompatible peer dependency to "@angular/common" (requires "^21.2.0", would install "22.2.5").
+
Package "@acme/ui-forms" has an incompatible peer dependency to "@angular/forms" (requires "^21.2.0", would install "22.2.5").
+
Incompatible peer dependencies found. See above for details. …
+
+
+ +
ng update quotes "^21.2.0"the Angular peers of the old 21.4.0 releases
+
The registry has 22.0.0with "^22.0.0" peers, published minutes earlier
+ +
+
The cause is pnpm 12's minimum release age,
+
a supply-chain safeguard that won't resolve versions published very recently.
+
+ +
+
Claude Code · scratch copy of acme-portal
+
+
T=$(mktemp -d) && … cp package.json pnpm-workspace.yaml …
+
pnpm add @acme/ui-core@22.0.0 --lockfile-only
+
Added 1 entry to minimumReleaseAgeExclude …:
@acme/ui-core@22.0.0
+
---with exclusion
+
✓ Lockfile passes supply-chain policies (561 entries in 2.1s)
+
+
+ +
+
pnpm-workspace.yaml
+
+
minimumReleaseAgeExclude:
+
- '@acme/ui-core@21.4.0'
+
- '@acme/ui-forms@21.4.0'
+
+ - '@acme/ui-core@22.0.0'
+
+ - '@acme/ui-forms@22.0.0'
+
+
+ +
+
Claude Code · acme-portal
+
+
… git add pnpm-workspace.yaml && git commit …
+
Permission for this action was denied by the Claude Code auto mode classifier.
+
Reason: [Security Weaken].
+
+
+ +
+

Which do you want?

+
1Exempt the two versions. This is what I recommend.
+
2Wait until 22.0.0 is older than the limit.
+
+ +
+

The system working

+ +
diff --git a/docs/video/enterprise/scenes/setup.html b/docs/video/enterprise/scenes/setup.html new file mode 100644 index 0000000..03330e5 --- /dev/null +++ b/docs/video/enterprise/scenes/setup.html @@ -0,0 +1,52 @@ +
+ acme-ui +
the libraries
+
+ @acme/ui-core +
buttons, cards, design tokens
+
+
+ @acme/ui-forms +
form fields, built on ui-core
+
+
+ +
+ acme-portal +
the app
+
+ package.json +
@acme/ui-core ^21.4.0
+
@acme/ui-forms ^21.4.0
+
+
+ .devcontainer +
pins Node 22.12.0
+
the team builds and serves here
+
+
+ + + +
+ Private registry +
At work: Artifactory or GitHub Packages
+
+ Verdaccio +
localhost:4873
+
+
+ + + + +
+ Windows host +
the agent edits, builds and tests here
+
+ Claude Code + ng-hop +
with ng-hop installed
+
ng-hop is a skill: instructions and scripts it loads when you ask about an Angular upgrade
+
+
diff --git a/docs/video/enterprise/scenes/takeaways.html b/docs/video/enterprise/scenes/takeaways.html new file mode 100644 index 0000000..34b843d --- /dev/null +++ b/docs/video/enterprise/scenes/takeaways.html @@ -0,0 +1,18 @@ +
Habits for your own upgrades
+ +
+
1Ask for the plan before the change
+
2One major at a time, with a commit for each
+
3Read the hand fixes first
+
4Your own libraries go first, and publishing is your call
+
5When the agent stops to ask, that's the key moment
+
6Check the result where your team works
+
+ +
ng-hop
+ +
+

Run it yourself

+

github.com/hazeliscoding/ng-hop

+

examples/enterprise

+
diff --git a/docs/video/enterprise/script.mjs b/docs/video/enterprise/script.mjs new file mode 100644 index 0000000..dcccf3a --- /dev/null +++ b/docs/video/enterprise/script.mjs @@ -0,0 +1,193 @@ +// The tutorial's narration, chapter by chapter. `text` is the caption, `say` what the voice reads when it differs. +// Each chapter's visuals are in scenes/.html, where data-cue="s12" brings an element in as sentence s12 starts. +import { existsSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = fileURLToPath(new URL('.', import.meta.url)); +const scene = (id) => { const file = join(ROOT, 'scenes', `${id}.html`); return existsSync(file) ? readFileSync(file, 'utf8') : ''; }; + +const css = String.raw` + @font-face { font-family: "JetBrains Mono"; font-weight: 400; src: url("assets/fonts/jetbrains-mono-latin-400-normal.woff2") format("woff2"); } + @font-face { font-family: "JetBrains Mono"; font-weight: 700; src: url("assets/fonts/jetbrains-mono-latin-700-normal.woff2") format("woff2"); } + :root { + --ground: #061717; + --panel: #0c2423; + --panel-2: #10302e; + --line: #1d4a46; + --ink: #e7f6f3; + --muted: #8db5af; + --teal: #2dd4bf; + --teal-ink: #042f2e; + --amber: #f5a524; + --red: #f87171; + --green: #4ade80; + --mono: "JetBrains Mono", monospace; + --sans: system-ui, sans-serif; + } + *, *::before, *::after { box-sizing: border-box; } + html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: var(--ground); } + #root { + position: relative; width: 100%; height: 100%; overflow: hidden; + font-family: var(--sans); color: var(--ink); + background: radial-gradient(1200px 700px at 85% -10%, #0f3a36 0%, transparent 60%), var(--ground); + } + [data-composition-id="main"] > div[data-composition-src] { position: absolute; inset: 0; } + .scene { position: absolute; inset: 0; } + .progress { position: absolute; left: 0; top: 0; width: 100%; height: 5px; background: var(--teal); opacity: 0.6; transform-origin: 0 50%; } + .chapter { position: absolute; left: 96px; top: 52px; display: flex; gap: 16px; align-items: center; font-size: 28px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); } + .chapter-n { display: inline-grid; place-items: center; width: 42px; height: 42px; border-radius: 11px; background: var(--teal); color: var(--teal-ink); font-weight: 800; letter-spacing: 0; } + .captions { position: absolute; left: 0; right: 0; bottom: 40px; height: 120px; } + .cap { position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); width: max-content; max-width: 1560px; margin: 0; padding: 14px 30px; border-radius: 14px; background: rgba(2, 12, 12, 0.82); font-size: 32px; line-height: 1.35; text-align: center; text-wrap: balance; } + .panel { position: absolute; background: var(--panel); border: 1px solid var(--line); border-radius: 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); } + .term { font-family: var(--mono); font-size: 28px; line-height: 1.5; overflow: hidden; } + .term-bar { display: flex; gap: 10px; align-items: center; padding: 14px 20px; border-bottom: 1px solid var(--line); color: var(--muted); font-family: var(--sans); font-size: 28px; } + .term-bar .dot { width: 14px; height: 14px; border-radius: 50%; background: #2b5450; } + .term-bar .where { margin-left: 10px; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .term-body { padding: 14px 26px 20px; } + .term-body > div { white-space: pre-wrap; } + .you { color: var(--teal); font-weight: 700; } + .you::before { content: "you › "; color: var(--muted); font-weight: 400; } + .cmd { color: var(--ink); } + .cmd::before { content: "$ "; color: var(--muted); } + .out { color: #cfe6e2; } + .dim { color: var(--muted); } + .err { color: var(--red); } + .ok { color: var(--green); } + .warn { color: var(--amber); } + .h1 { font-size: 84px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.05; } + .h2 { font-size: 52px; font-weight: 750; letter-spacing: -0.01em; line-height: 1.15; } + .lead { font-size: 36px; color: var(--muted); line-height: 1.35; } + .card { padding: 28px 34px; font-size: 30px; line-height: 1.42; } + .card h3 { margin: 0 0 14px; font-size: 28px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--teal); } + .card p { margin: 0 0 10px; } + .card ul { margin: 0; padding-left: 1.1em; } + .card li { margin: 0 0 8px; } + .aside { border: 2px solid var(--amber); background: #1d1a0e; } + .aside h3 { color: var(--amber); } + .stop { border: 2px solid var(--teal); } + .shot { position: absolute; border-radius: 18px; overflow: hidden; border: 1px solid var(--line); box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45); background: #f3f5f9; } + .shot img { display: block; width: 100%; } + .badge { position: absolute; padding: 8px 18px; border-radius: 999px; background: var(--teal); color: var(--teal-ink); font-weight: 800; font-size: 30px; } + .badge.amber { background: var(--amber); color: #2a1a00; } + .diff { font-family: var(--mono); font-size: 28px; line-height: 1.5; } + .diff .add { color: var(--green); } + .diff .del { color: var(--red); } + .box { position: absolute; padding: 22px 26px; border-radius: 18px; background: var(--panel); border: 2px solid var(--line); font-size: 28px; line-height: 1.4; color: var(--muted); } + .box b { display: block; margin-bottom: 6px; font-size: 32px; color: var(--ink); } + .box.teal { border-color: var(--teal); } + .box.amber { border-color: var(--amber); } + .link { position: absolute; height: 0; border-top: 4px dashed var(--teal); } + .link-label { position: absolute; white-space: nowrap; font-size: 28px; color: var(--teal); letter-spacing: 0.04em; } + .ticks { list-style: none; margin: 0; padding: 0; } + .ticks li { margin: 0 0 10px; padding-left: 1.4em; text-indent: -1.4em; } + .ticks li::before { content: "✓ "; color: var(--green); font-weight: 800; } + .num { display: inline-grid; place-items: center; width: 52px; height: 52px; margin-right: 18px; border-radius: 14px; background: var(--panel-2); border: 2px solid var(--teal); color: var(--teal); font-weight: 800; font-size: 28px; } +`; + +export default { + title: 'An enterprise Angular upgrade, with an agent', + voice: 'af_heart', + speed: 1, + css, + chapters: [ + { + id: 'intro', title: 'Why this is hard', + sentences: [ + { id: 's1', text: "This is Acme's portal, an Angular 21 app, and an AI coding agent is going to take it to Angular 22." }, + { id: 's2', text: "For most apps that's one command. This one is built on Acme's own UI libraries, and they only accept Angular 21." }, + { id: 's3', text: 'So the libraries have to move first and be published, and only then can the app follow.' }, + { id: 's4', text: "You'll see the agent run into real problems along the way, and how it handles them." }, + { id: 's5', text: "If you're new to working with an agent, watch where it stops. It plans before it changes anything, and it asks before most things that are yours to decide." }, + ], + }, + { + id: 'setup', title: 'The setup', + sentences: [ + { id: 's6', text: 'The code lives in two repositories, the way a company usually splits it.' }, + { id: 's7', text: 'acme-ui holds the libraries. ui-core has the buttons, cards and design tokens, and ui-forms builds its form fields on top of ui-core.', say: 'Acme UI holds the libraries. UI core has the buttons, cards and design tokens, and UI forms builds its form fields on top of UI core.' }, + { id: 's8', text: "They're published to a private registry. At work that's something like Artifactory or GitHub Packages. Here it's Verdaccio, running locally.", say: "They're published to a private registry. At work that's something like Artifactory or GitHub Packages. Here it's Verdacho, running locally." }, + { id: 's9', text: "acme-portal is the app. It installs both libraries from that registry, and its dev container pins the team's Node version.", say: "Acme portal is the app. It installs both libraries from that registry, and its dev container pins the team's Node version." }, + { id: 's10', text: "The agent runs on Windows, on the host, not inside the container. It edits, builds and tests on the machine, and the container is where the team builds and serves." }, + { id: 's11', text: 'The agent is Claude Code, with ng-hop installed. ng-hop is a skill: instructions and scripts the agent loads when you ask about an Angular upgrade.', say: 'The agent is Claude Code, with N G hop installed. N G hop is a skill: instructions and scripts the agent loads when you ask about an Angular upgrade.' }, + ], + }, + { + id: 'ask', title: 'Ask, and let it plan', + sentences: [ + { id: 's12', text: 'In the portal, the whole request is one sentence.' }, + { id: 's13', text: "The agent loads ng-hop and runs its plan first. The plan reads what's installed from the lockfile and what's published from the registry, and changes nothing.", say: "The agent loads N G hop and runs its plan first. The plan reads what's installed from the lockfile and what's published from the registry, and changes nothing." }, + { id: 's14', text: 'The answer is: not today. Both libraries only accept Angular 21.' }, + { id: 's15', text: "The plan also sees that they come from Acme's own registry, so they're most likely the team's. And it gives the order: ui-core first, because ui-forms needs it.", say: "The plan also sees that they come from Acme's own registry, so they're most likely the team's. And it gives the order: UI core first, because UI forms needs it." }, + { id: 's16', text: "So the agent stops. It hasn't touched either repository, and it recommends upgrading the libraries first, because waiting for them would only mean waiting on your own team." }, + { id: 's17', text: "If you're newer to this: a library's peer range says which Angular versions it accepts. An agent could force its way past it. That's how upgrades can break at runtime, so a good agent gives you the choice." }, + { id: 's18', text: 'If you lead a team, this is the call you want a person to make, and the agent brings the facts for it: who owns each library, what it needs, and how much the app uses it.' }, + ], + }, + { + id: 'libraries', title: 'The libraries go first', + sentences: [ + { id: 's19', text: 'The reply is: yes, start with acme-ui.', say: 'The reply is: yes, start with Acme UI.' }, + { id: 's20', text: 'In the library repository, the agent creates a branch and records a starting point. Both libraries build, and their tests pass.' }, + { id: 's21', text: "It reads Angular's own update guide for 21 to 22, then runs ng update with Angular, the CLI and ng-packagr named together, so nothing has to be forced.", say: "It reads Angular's own update guide for twenty-one to twenty-two, then runs N G update with Angular, the CLI and N G packager named together, so nothing has to be forced." }, + { id: 's22', text: "Then comes the part ng update doesn't do for a library. Each library's version goes to 22.0.0, its Angular peer ranges move to the new major, and ui-forms now needs ui-core 22.", say: "Then comes the part N G update doesn't do for a library. Each library's version goes to twenty-two point zero point zero, its Angular peer ranges move to the new major, and UI forms now needs UI core twenty-two." }, + { id: 's23', text: 'It builds both, in order, runs the tests again, and commits.' }, + { id: 's24', text: "Its report puts the hand fixes first, because that's what a reviewer has to read. Then come the migrations, the scripts ng update runs to rewrite your code. One of them kept the old change detection on every component, since Angular 22 changes the default.", say: "Its report puts the hand fixes first, because that's what a reviewer has to read. Then come the migrations, the scripts N G update runs to rewrite your code. One of them kept the old change detection on every component, since Angular twenty-two changes the default." }, + { id: 's25', text: "It even read ng-packagr's changelog, and pointed out that the libraries are now bundled by a different tool, which is worth a look in the app.", say: "It even read N G packager's changelog, and pointed out that the libraries are now bundled by a different tool, which is worth a look in the app." }, + { id: 's26', text: "Then it stops again. Publishing changes what every app installs, and a version number can't be published twice." }, + ], + }, + { + id: 'publish', title: 'Publish, and a safeguard', + sentences: [ + { id: 's27', text: 'Yes, publish both, then upgrade the portal.' }, + { id: 's28', text: 'Both releases go to the registry. Back in the portal, the plan now says Angular 22 is reachable, and the agent records the starting point there: the build, the test, and the app running in Chrome.' }, + { id: 's29', text: "Then ng update refuses. It still checks the old 21.4.0 releases, which only accept Angular 21, though version 22 was published a minute ago.", say: "Then N G update refuses. It still checks the old twenty-one point four releases, which only accept Angular twenty-one, though version twenty-two was published a minute ago." }, + { id: 's30', text: "The agent traces it to pnpm 12's minimum release age: a release that new needs an exemption first. It's a guard against a compromised package reaching you before anyone notices.", say: "The agent traces it to P N P M twelve's minimum release age: a release that new needs an exemption first. It's a guard against a compromised package reaching you before anyone notices." }, + { id: 's31', text: "It spots that the team already exempted the 21.4.0 releases from that rule, in pnpm's settings file, and a scratch copy of the project shows 22.0.0 needs the same.", say: "It spots that the team already exempted the twenty-one point four releases from that rule, in P N P M's settings file, and a scratch copy of the project shows twenty-two point zero needs the same." }, + { id: 's32', text: "When it tries to add the same exemption for 22.0.0, Claude Code's own safety check stops it, because that relaxes a security setting. So it asks.", say: "When it tries to add the same exemption for twenty-two point zero, Claude Code's own safety check stops it, because that relaxes a security setting. So it asks." }, + { id: 's33', text: "That's the system working. The agent can explain a safeguard, but relaxing one is a person's decision." }, + ], + }, + { + id: 'age', title: 'A rule inside ng update', + sentences: [ + { id: 's34', text: 'The answer: yes, exempt the two versions, like before.' }, + { id: 's35', text: 'The exemption is committed, and ng update still refuses.', say: 'The exemption is committed, and N G update still refuses.' }, + { id: 's36', text: "This time the agent reads the Angular CLI's own source. It finds that ng update applies the age rule itself, 24 hours by default, and never reads pnpm's exemption list.", say: "This time the agent reads the Angular CLI's own source. It finds that N G update applies the age rule itself, twenty-four hours by default, and never reads P N P M's exemption list." }, + { id: 's37', text: 'It offers two ways forward: wait until tomorrow, or install the two exempted releases with pnpm first, and then run ng update for Angular.', say: 'It offers two ways forward: wait until tomorrow, or install the two exempted releases with P N P M first, and then run N G update for Angular.' }, + { id: 's38', text: "The second runs ng update on uncommitted changes, with a flag the skill normally avoids, so it says so, and asks.", say: "The second runs N G update on uncommitted changes, with a flag the skill normally avoids, so it says so, and asks." }, + { id: 's39', text: 'Go with option two.' }, + { id: 's40', text: "Now ng update's peer check sees the real 22.0.0 releases and passes, and every migration runs.", say: "Now N G update's peer check sees the real twenty-two point zero releases and passes, and every migration runs." }, + { id: 's41', text: 'The portal builds, its test passes, and the app renders in Chrome, the same as on Angular 21.' }, + { id: 's42', text: "The report lists what was done by hand, why the install order changed, and one flow it didn't exercise: submitting the request form, which is worth a click by hand." }, + ], + }, + { + id: 'container', title: 'Check where the team works', + sentences: [ + { id: 's43', text: 'One line of that report was wrong. It said neither repository pins a Node version.' }, + { id: 's44', text: "Open the dev container, and the build stops. It still runs Node 22.12, which is too old for Angular 22.", say: "Open the dev container, and the build stops. It still runs Node twenty-two point twelve, which is too old for Angular twenty-two." }, + { id: 's45', text: 'Told about it, the agent says plainly that it was its mistake: it checked the library repository for Node pins, but not the portal.' }, + { id: 's46', text: "It moves the dev container to Node 22.23.4, staying on the team's Node line, and tests the image before saying it's done.", say: "It moves the dev container to Node twenty-two point twenty-three point four, staying on the team's Node line, and tests the image before saying it's done." }, + { id: 's47', text: 'Rebuilt, the container runs Node 22.23.4, builds the app, and serves it.', say: 'Rebuilt, the container runs Node twenty-two point twenty-three point four, builds the app, and serves it.' }, + { id: 's48', text: "After this run, ng-hop was updated too. Its plan now finds Node pins itself, in Dockerfiles, dev containers, version files and GitHub workflows, so next time the portal's plan names this one.", say: "After this run, N G hop was updated too. Its plan now finds Node pins itself, in Dockerfiles, dev containers, version files and GitHub workflows, so next time the portal's plan names this one." }, + { id: 's49', text: 'This is why you check the result where your team actually works, not only where the agent ran.' }, + ], + }, + { + id: 'takeaways', title: 'What to take away', + hold: 2, + sentences: [ + { id: 's50', text: 'A few habits carry over to your own upgrades.' }, + { id: 's51', text: "Ask for the plan before the change. It's cheap, and it tells you what's in the way." }, + { id: 's52', text: 'Go one major at a time, with a commit for each, so any stop is a safe place to stop.' }, + { id: 's53', text: "Read the hand fixes first. They're the parts no tool did for you." }, + { id: 's54', text: 'When your own libraries block you, they go first, and publishing is your call.' }, + { id: 's55', text: "When the agent stops to ask, that's usually the most important moment of the run." }, + { id: 's56', text: 'And check the result where your team works.' }, + { id: 's57', text: "The example is in the ng-hop repository, under examples, enterprise, if you'd like to run it yourself.", say: "The example is in the N G hop repository, under examples, enterprise, if you'd like to run it yourself." }, + ], + }, + ].map((chapter) => ({ ...chapter, visuals: scene(chapter.id) })), +}; diff --git a/docs/video/enterprise/tools/build.mjs b/docs/video/enterprise/tools/build.mjs new file mode 100644 index 0000000..cdca30c --- /dev/null +++ b/docs/video/enterprise/tools/build.mjs @@ -0,0 +1,127 @@ +// Builds the HyperFrames project from script.mjs and timings.json: index.html lays the chapters end to end with their +// narration, and compositions/.html holds each chapter's visuals and captions. In a visual, an element with +// data-cue="s12" appears when sentence s12 starts ("s12+0.8" a little after, "s12$" at its end), and data-out takes it +// away the same way. Usage: node tools/build.mjs +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const ROOT = fileURLToPath(new URL('..', import.meta.url)); +const script = (await import(pathToFileURL(join(ROOT, 'script.mjs')).href)).default; +const timings = JSON.parse(readFileSync(join(ROOT, 'timings.json'), 'utf8')); +mkdirSync(join(ROOT, 'compositions'), { recursive: true }); + +const escape = (s) => String(s).replace(/&/g, '&').replace(//g, '>'); + +function cueTime(cue, chapterId) { + if (cue === undefined || cue === '0') return 0; + const m = /^(s[\w-]+)(\$)?([+-][\d.]+)?$/.exec(cue); + if (!m) throw new Error(`${chapterId}: bad cue ${cue}`); + const s = timings[chapterId].sentences.find((x) => x.id === m[1]); + if (!s) throw new Error(`${chapterId}: no sentence ${m[1]} for cue ${cue}`); + return Number(((m[2] ? s.end : s.start) + Number(m[3] || 0)).toFixed(3)); +} + +function chapterHtml(chapter, index) { + const t = timings[chapter.id]; + let n = 0; + const steps = []; + // Give every cued element an id and a timeline step, in document order. + const html = chapter.visuals.replace(/<([a-z][\w-]*)([^>]*?)\sdata-cue="([^"]+)"([^>]*)>/g, (all, tag, before, cue, after) => { + const id = `${chapter.id}-c${++n}`; + const out = /data-out="([^"]+)"/.exec(all)?.[1]; + const kind = /class="[^"]*\bpop\b/.test(all) ? 'pop' : 'rise'; + const step = { id, at: cueTime(cue, chapter.id), out: out && cueTime(out, chapter.id), kind }; + // A cue past its sentence's end shows the element over the next sentence: say so, since narration edits move them. + const sentence = /^(s[\w-]+)/.exec(cue)?.[1]; + const end = sentence && t.sentences.find((x) => x.id === sentence)?.end; + if (end !== undefined && step.at > end + 0.5) console.warn(` ${chapter.id}: ${cue} lands ${(step.at - end).toFixed(1)}s after ${sentence} ends`); + if (step.at >= t.duration || (step.out !== undefined && step.out > t.duration)) console.warn(` ${chapter.id}: ${cue} is past the chapter's end`); + steps.push(step); + return `<${tag} id="${id}"${before}${after}>`; + }); + // A thin bar across the top fills over the chapter, so the frame is never quite still while the narration runs. + steps.push({ id: `${chapter.id}-progress`, at: 0, kind: 'progress', duration: t.duration }); + const captions = chapter.sentences.map((s, i) => { + const c = t.sentences[i]; + // A caption stays until the next one starts, so a short sentence is on screen long enough to read. + const next = t.sentences[i + 1]; + steps.push({ id: `${chapter.id}-cap${i}`, at: c.start, out: next ? next.start - 0.05 : Math.min(c.end + 0.8, t.duration - 0.05), kind: 'caption' }); + return `

${escape(s.text)}

`; + }).join('\n '); + const tweens = steps.map((s) => { + const lines = []; + if (s.kind === 'progress') lines.push(`tl.fromTo("#${s.id}", { scaleX: 0 }, { scaleX: 1, duration: ${s.duration}, ease: "none" }, 0);`); + else if (s.kind === 'caption') lines.push(`tl.fromTo("#${s.id}", { opacity: 0 }, { opacity: 1, duration: 0.15 }, ${s.at});`); + else if (s.kind === 'pop') lines.push(`tl.fromTo("#${s.id}", { opacity: 0, scale: 0.92 }, { opacity: 1, scale: 1, duration: 0.4, ease: "back.out(1.6)" }, ${s.at});`); + else lines.push(`tl.fromTo("#${s.id}", { opacity: 0, y: 18 }, { opacity: 1, y: 0, duration: 0.45, ease: "power2.out" }, ${s.at});`); + if (s.out !== undefined) lines.push(`tl.to("#${s.id}", { opacity: 0, duration: ${s.kind === 'caption' ? 0.15 : 0.35} }, ${s.out});`); + return lines.join('\n '); + }).join('\n '); + return ` +`; +} + +let at = 0; +const slots = []; +const audio = []; +script.chapters.forEach((chapter, i) => { + const d = timings[chapter.id].duration; + writeFileSync(join(ROOT, 'compositions', `${chapter.id}.html`), chapterHtml(chapter, i)); + slots.push(`
`); + audio.push(``); + at += d; +}); +const total = Number(at.toFixed(3)); + +writeFileSync(join(ROOT, 'index.html'), ` + + + + + ${escape(script.title)} + + + + +
+ ${slots.join('\n ')} + ${audio.join('\n ')} +
+ + + +`); + +// Chapters for the video's description, as YouTube reads them. +const chapters = []; +let start = 0; +for (const chapter of script.chapters) { + chapters.push(`${Math.floor(start / 60)}:${String(Math.floor(start % 60)).padStart(2, '0')} ${chapter.title}`); + start += timings[chapter.id].duration; +} +writeFileSync(join(ROOT, 'chapters.txt'), `${chapters.join('\n')}\n`); +console.log(`built ${script.chapters.length} chapters, ${Math.floor(total / 60)}:${String(Math.round(total % 60)).padStart(2, '0')}`); diff --git a/docs/video/enterprise/tools/voice.mjs b/docs/video/enterprise/tools/voice.mjs new file mode 100644 index 0000000..5be0340 --- /dev/null +++ b/docs/video/enterprise/tools/voice.mjs @@ -0,0 +1,67 @@ +// Narration: one WAV per sentence from Kokoro (through the HyperFrames CLI), cached by its spoken text, then one WAV per +// chapter with a lead-in, a pause between sentences and a tail, plus timings.json with each sentence's start and end +// within its chapter. Usage: node tools/voice.mjs (needs HYPERFRAMES_PYTHON pointing at a Python with kokoro-onnx) +import { execFileSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const ROOT = fileURLToPath(new URL('..', import.meta.url)); +const { chapters, voice = 'af_heart', speed = 1 } = (await import(pathToFileURL(join(ROOT, 'script.mjs')).href)).default; +const CACHE = join(ROOT, '.voice'); +const ASSETS = join(ROOT, 'assets'); +mkdirSync(CACHE, { recursive: true }); +mkdirSync(ASSETS, { recursive: true }); +const LEAD = 0.7; +const GAP = 0.5; +const TAIL = 1.0; + +// npx is a .cmd shim on Windows, which only runs through a shell, so the text goes in a file rather than on the command +// line, and every argument here is a plain path or option with no spaces. +const run = (cmd, args) => execFileSync(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], shell: cmd === 'npx' && process.platform === 'win32' }); +const duration = (file) => Number(run('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', file]).trim()); + +function speak(text) { + const base = join(CACHE, createHash('sha256').update(`${voice}|${speed}|${text}`).digest('hex').slice(0, 16)); + if (!existsSync(`${base}.wav`)) { + writeFileSync(`${base}.txt`, text); + run('npx', ['--yes', 'hyperframes@0.9.22', 'tts', `${base}.txt`, '--voice', voice, '--speed', String(speed), '--output', `${base}.wav`, '--json']); + } + return `${base}.wav`; +} + +function silence(seconds, like) { + const file = join(CACHE, `silence-${seconds}.wav`); + if (!existsSync(file)) { + const [rate, channels] = run('ffprobe', ['-v', 'error', '-select_streams', 'a:0', '-show_entries', 'stream=sample_rate,channels', '-of', 'csv=p=0', like]).trim().split(','); + run('ffmpeg', ['-y', '-v', 'error', '-f', 'lavfi', '-i', `anullsrc=r=${rate}:cl=${channels === '1' ? 'mono' : 'stereo'}`, '-t', String(seconds), '-c:a', 'pcm_s16le', file]); + } + return file; +} + +const timings = {}; +for (const chapter of chapters) { + const parts = []; + const sentences = []; + let at = LEAD; + for (const s of chapter.sentences) { + const file = speak(s.say || s.text); + const length = duration(file); + sentences.push({ id: s.id, start: Number(at.toFixed(3)), end: Number((at + length).toFixed(3)) }); + parts.push(file); + at += length + (s.pause ?? GAP); + process.stdout.write('.'); + } + const total = Number((at - GAP + TAIL + (chapter.hold || 0)).toFixed(3)); + // The concat demuxer needs one format throughout, so the pauses are made like the speech. + const list = [silence(LEAD, parts[0]), ...parts.flatMap((p, i) => (i < parts.length - 1 ? [p, silence(chapter.sentences[i].pause ?? GAP, p)] : [p])), silence(TAIL + (chapter.hold || 0), parts[0])]; + const listFile = join(CACHE, `${chapter.id}.txt`); + writeFileSync(listFile, list.map((f) => `file '${f.replace(/\\/g, '/')}'`).join('\n')); + run('ffmpeg', ['-y', '-v', 'error', '-f', 'concat', '-safe', '0', '-i', listFile, '-c', 'copy', join(ASSETS, `vo-${chapter.id}.wav`)]); + timings[chapter.id] = { duration: total, sentences }; + console.log(`\n${chapter.id}: ${chapter.sentences.length} sentences, ${total.toFixed(1)}s`); +} +writeFileSync(join(ROOT, 'timings.json'), `${JSON.stringify(timings, null, 2)}\n`); +const all = Object.values(timings).reduce((sum, c) => sum + c.duration, 0); +console.log(`total ${Math.floor(all / 60)}:${String(Math.round(all % 60)).padStart(2, '0')}`); diff --git a/examples/enterprise/README.md b/examples/enterprise/README.md new file mode 100644 index 0000000..f255e75 --- /dev/null +++ b/examples/enterprise/README.md @@ -0,0 +1,48 @@ +# An enterprise upgrade + +Acme's portal is on Angular 21 and can't go to 22 on its own. Its UI comes from Acme's own libraries, and they only accept Angular 21, so they have to be upgraded and published first, and the app after them. This sets that up on one machine, the way a company has it: the libraries in a repository of their own, published to a private registry, and the app in another, with a dev container for the team. + +## What's here + +- `acme-ui/`: the libraries, `@acme/ui-core` and `@acme/ui-forms`, which needs ui-core. Both at 21.4.0, with peer ranges that stop at Angular 21. +- `acme-portal/`: the app that uses them, with a dev container pinned to Node 22.12. Angular 21 accepts that Node, and Angular 22 doesn't. +- `registry/config.yaml`: a local [Verdaccio](https://verdaccio.org) that stands in for the company registry. Each repo's `.npmrc` sends only the `@acme` scope there. +- `setup.mjs`: copies both repos to a folder of your choice, publishes the 21.4.0 libraries and makes each a git repository. + +## Set it up + +You need Node 24.15 or newer, pnpm 12 and git, and Docker Desktop for the dev container. Verdaccio 7 needs Node 24 or newer, and Angular 22, which the upgrade moves to, needs 24.15 or newer on Node 24. + +1. Start the registry, and leave it running: + ```sh + npx verdaccio@7.0.2 --config examples/enterprise/registry/config.yaml + ``` +2. In another terminal: + ```sh + node examples/enterprise/setup.mjs + ``` +3. Open `/acme-portal` in your agent, with ng-hop installed, and ask: "Upgrade this app to Angular 22." + +To start over, stop the registry, delete `examples/enterprise/registry/storage/` and start it again. The registry keeps what each run publishes, and once the libraries' 22.0.0 is in it, the portal isn't blocked any more. + +## What should happen + +1. The plan says the portal is blocked by `@acme/ui-core` and `@acme/ui-forms`, that both come from the registry your npm config sets for `@acme`, and that, if they're your team's, ui-core goes first. +2. Asked where they live, you point the agent at `/acme-ui`. It upgrades that workspace to Angular 22, moves each library's peer ranges and version, builds and tests them, and asks before publishing. +3. Back in the portal, the plan is clear. But the libraries' 22.0.0 is only minutes old, and from Angular CLI 22.1, `ng update` skips a release younger than pnpm's minimum release age, a day by default, and reads no exemption list. So the agent asks: wait a day, or exempt the two releases in a commit of their own, the way `pnpm-workspace.yaml` already exempts 21.4.0, and install them before it updates Angular. Then the agent takes the app to 22, raises the dev container's Node to one Angular 22 accepts, and checks the build, the tests and the running app. + +The upgrade runs from your machine, not inside the dev container. The container has its own Node and its own `node_modules`, so rebuild it afterwards and check the app there. In VS Code, run "Dev Containers: Rebuild and Reopen in Container", or "Dev Containers: Rebuild Container" if you're already in it, then `pnpm run build` and `pnpm test --watch=false` in its terminal. From a terminal: + +```sh +npx @devcontainers/cli up --remove-existing-container --workspace-folder /acme-portal +npx @devcontainers/cli exec --workspace-folder /acme-portal pnpm run build +npx @devcontainers/cli exec --workspace-folder /acme-portal pnpm test --watch=false +``` + +A plain `up` keeps a container made before the upgrade, on its old Node, and it reports success once the install is done, before anything runs Angular. `--watch=false` makes the tests run once and stop, since in a terminal they'd wait for changes. + +The container reaches the registry on your machine through `.devcontainer/registry-forward.mjs` and Docker Desktop's `host.docker.internal`, which leads to your machine's 127.0.0.1, where the registry listens. Docker Engine on Linux has no such name by default, so there the container can't install the `@acme` packages. + +## On video + +The M7 tutorial follows one run of this, from the plan to the rebuilt container. Its source is in [`docs/video/enterprise`](../../docs/video/enterprise/). diff --git a/examples/enterprise/acme-portal/.devcontainer/Dockerfile b/examples/enterprise/acme-portal/.devcontainer/Dockerfile new file mode 100644 index 0000000..af0c482 --- /dev/null +++ b/examples/enterprise/acme-portal/.devcontainer/Dockerfile @@ -0,0 +1,11 @@ +# The team's Node, pinned. Angular 21 accepts it, and Angular 22 needs ^22.22.3, ^24.15.0 or >=26. +FROM node:22.12.0-bookworm + +# Node 22.12's own corepack can't verify current pnpm releases, so corepack 0.34.7 runs pnpm from package.json's +# packageManager. It's the last corepack that supports Node 22.12, and it's pinned so the image doesn't change with +# corepack's next release. node_modules is a volume owned by the node user, so the container's packages never mix with +# a Windows host's. +RUN npm install -g corepack@0.34.7 \ + && corepack enable \ + && mkdir -p /workspaces/acme-portal/node_modules \ + && chown -R node:node /workspaces/acme-portal diff --git a/examples/enterprise/acme-portal/.devcontainer/devcontainer.json b/examples/enterprise/acme-portal/.devcontainer/devcontainer.json new file mode 100644 index 0000000..db79b5c --- /dev/null +++ b/examples/enterprise/acme-portal/.devcontainer/devcontainer.json @@ -0,0 +1,11 @@ +{ + "name": "acme-portal", + "build": { "dockerfile": "Dockerfile" }, + "workspaceFolder": "/workspaces/acme-portal", + "remoteUser": "node", + "containerEnv": { "COREPACK_ENABLE_DOWNLOAD_PROMPT": "0", "NG_CLI_ANALYTICS": "false" }, + "mounts": ["source=acme-portal-${devcontainerId}-node-modules,target=/workspaces/acme-portal/node_modules,type=volume"], + "postCreateCommand": "nohup node .devcontainer/registry-forward.mjs > /tmp/registry-forward.log 2>&1 & pnpm install --frozen-lockfile", + "postStartCommand": "nohup node .devcontainer/registry-forward.mjs > /tmp/registry-forward.log 2>&1 &", + "forwardPorts": [4200] +} diff --git a/examples/enterprise/acme-portal/.devcontainer/registry-forward.mjs b/examples/enterprise/acme-portal/.devcontainer/registry-forward.mjs new file mode 100644 index 0000000..26a8532 --- /dev/null +++ b/examples/enterprise/acme-portal/.devcontainer/registry-forward.mjs @@ -0,0 +1,15 @@ +// The company registry runs on the host at localhost:4873, which inside the container is the container itself. This +// forwards the container's localhost:4873 to the host, so .npmrc can name one address for both. +import { connect, createServer } from 'node:net'; + +const server = createServer((client) => { + const host = connect(4873, 'host.docker.internal'); + client.pipe(host).pipe(client); + for (const end of [client, host]) end.on('error', () => { client.destroy(); host.destroy(); }); +}); +server.on('error', (err) => { + // Already forwarding, from an earlier start. + if (err.code === 'EADDRINUSE') process.exit(0); + throw err; +}); +server.listen(4873, '127.0.0.1'); diff --git a/examples/enterprise/acme-portal/.editorconfig b/examples/enterprise/acme-portal/.editorconfig new file mode 100644 index 0000000..f166060 --- /dev/null +++ b/examples/enterprise/acme-portal/.editorconfig @@ -0,0 +1,17 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single +ij_typescript_use_double_quotes = false + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/examples/enterprise/acme-portal/.gitattributes b/examples/enterprise/acme-portal/.gitattributes new file mode 100644 index 0000000..ba64ad5 --- /dev/null +++ b/examples/enterprise/acme-portal/.gitattributes @@ -0,0 +1,2 @@ +# LF everywhere, so a Windows checkout and the dev container see the same files. +* text=auto eol=lf diff --git a/examples/enterprise/acme-portal/.gitignore b/examples/enterprise/acme-portal/.gitignore new file mode 100644 index 0000000..854acd5 --- /dev/null +++ b/examples/enterprise/acme-portal/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/enterprise/acme-portal/.npmrc b/examples/enterprise/acme-portal/.npmrc new file mode 100644 index 0000000..6832165 --- /dev/null +++ b/examples/enterprise/acme-portal/.npmrc @@ -0,0 +1,2 @@ +# Acme packages come from the company registry, a local Verdaccio in this example. +@acme:registry=http://localhost:4873/ diff --git a/examples/enterprise/acme-portal/.prettierrc b/examples/enterprise/acme-portal/.prettierrc new file mode 100644 index 0000000..d6c16d7 --- /dev/null +++ b/examples/enterprise/acme-portal/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] +} diff --git a/examples/enterprise/acme-portal/README.md b/examples/enterprise/acme-portal/README.md new file mode 100644 index 0000000..491e7c5 --- /dev/null +++ b/examples/enterprise/acme-portal/README.md @@ -0,0 +1,7 @@ +# acme-portal + +Acme's equipment-request app, built on `@acme/ui-core` and `@acme/ui-forms` from the company registry. + +- `pnpm start` serves it at http://localhost:4200. +- `pnpm test` runs its tests. +- The dev container in `.devcontainer/` gives everyone the same Node and pnpm. Its `node_modules` is a volume of its own, so a checkout used from both Windows and the container never mixes their packages. diff --git a/examples/enterprise/acme-portal/angular.json b/examples/enterprise/acme-portal/angular.json new file mode 100644 index 0000000..df3287f --- /dev/null +++ b/examples/enterprise/acme-portal/angular.json @@ -0,0 +1,74 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm" + }, + "newProjectRoot": "projects", + "projects": { + "acme-portal": { + "projectType": "application", + "schematics": {}, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "node_modules/@acme/ui-core/theme.css", + "src/styles.css" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "acme-portal:build:production" + }, + "development": { + "buildTarget": "acme-portal:build:development" + } + }, + "defaultConfiguration": "development" + }, + "test": { + "builder": "@angular/build:unit-test" + } + } + } + } +} diff --git a/examples/enterprise/acme-portal/package.json b/examples/enterprise/acme-portal/package.json new file mode 100644 index 0000000..b6dfa3b --- /dev/null +++ b/examples/enterprise/acme-portal/package.json @@ -0,0 +1,34 @@ +{ + "name": "acme-portal", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "packageManager": "pnpm@12.16.1", + "dependencies": { + "@acme/ui-core": "^21.4.0", + "@acme/ui-forms": "^21.4.0", + "@angular/common": "^21.2.0", + "@angular/compiler": "^21.2.0", + "@angular/core": "^21.2.0", + "@angular/forms": "^21.2.0", + "@angular/platform-browser": "^21.2.0", + "@angular/router": "^21.2.0", + "rxjs": "~7.8.0", + "tslib": "^2.3.0" + }, + "devDependencies": { + "@angular/build": "^21.2.29", + "@angular/cli": "^21.2.29", + "@angular/compiler-cli": "^21.2.0", + "jsdom": "^28.0.0", + "prettier": "^3.8.1", + "typescript": "~5.9.2", + "vitest": "^4.0.8" + } +} diff --git a/examples/enterprise/acme-portal/pnpm-workspace.yaml b/examples/enterprise/acme-portal/pnpm-workspace.yaml new file mode 100644 index 0000000..53c9f23 --- /dev/null +++ b/examples/enterprise/acme-portal/pnpm-workspace.yaml @@ -0,0 +1,9 @@ +# The team decided which dependencies may run install scripts: none of these need theirs to build or serve. +allowBuilds: + '@parcel/watcher': false + esbuild: false + lmdb: false + msgpackr-extract: false +minimumReleaseAgeExclude: + - '@acme/ui-core@21.4.0' + - '@acme/ui-forms@21.4.0' diff --git a/examples/enterprise/acme-portal/public/favicon.ico b/examples/enterprise/acme-portal/public/favicon.ico new file mode 100644 index 0000000..57614f9 Binary files /dev/null and b/examples/enterprise/acme-portal/public/favicon.ico differ diff --git a/examples/enterprise/acme-portal/src/app/app.config.ts b/examples/enterprise/acme-portal/src/app/app.config.ts new file mode 100644 index 0000000..cb1270e --- /dev/null +++ b/examples/enterprise/acme-portal/src/app/app.config.ts @@ -0,0 +1,11 @@ +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideRouter(routes) + ] +}; diff --git a/examples/enterprise/acme-portal/src/app/app.css b/examples/enterprise/acme-portal/src/app/app.css new file mode 100644 index 0000000..a5ab958 --- /dev/null +++ b/examples/enterprise/acme-portal/src/app/app.css @@ -0,0 +1,37 @@ +.masthead { + padding: 2rem 2rem 1rem; +} + +.masthead h1 { + margin: 0; +} + +.masthead p { + margin: 0.25rem 0 0; + color: var(--acme-muted); +} + +.layout { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); + gap: 1.5rem; + padding: 1rem 2rem 2rem; +} + +.requests { + margin: 0; + padding: 0; + list-style: none; + display: grid; + gap: 0.5rem; +} + +.requests li { + display: flex; + gap: 0.5rem; + align-items: baseline; +} + +.requests span { + color: var(--acme-muted); +} diff --git a/examples/enterprise/acme-portal/src/app/app.html b/examples/enterprise/acme-portal/src/app/app.html new file mode 100644 index 0000000..d29ff52 --- /dev/null +++ b/examples/enterprise/acme-portal/src/app/app.html @@ -0,0 +1,22 @@ +
+

Acme Portal

+

Equipment requests for the Acme team.

+
+ +
+ +
+ + + Send request + +
+ + +
    + @for (request of requests(); track $index) { +
  • {{ request.title }} {{ request.reason }}
  • + } +
+
+
diff --git a/examples/enterprise/acme-portal/src/app/app.routes.ts b/examples/enterprise/acme-portal/src/app/app.routes.ts new file mode 100644 index 0000000..dc39edb --- /dev/null +++ b/examples/enterprise/acme-portal/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import { Routes } from '@angular/router'; + +export const routes: Routes = []; diff --git a/examples/enterprise/acme-portal/src/app/app.spec.ts b/examples/enterprise/acme-portal/src/app/app.spec.ts new file mode 100644 index 0000000..5f84fd7 --- /dev/null +++ b/examples/enterprise/acme-portal/src/app/app.spec.ts @@ -0,0 +1,24 @@ +import { TestBed } from '@angular/core/testing'; +import { App } from './app'; + +describe('App', () => { + it('lists a new request at the top once it is sent', async () => { + const fixture = TestBed.createComponent(App); + await fixture.whenStable(); + const element = fixture.nativeElement as HTMLElement; + expect(element.querySelector('h1')?.textContent).toBe('Acme Portal'); + + const [title, reason] = Array.from(element.querySelectorAll('input')); + title.value = 'Laptop stand'; + title.dispatchEvent(new Event('input')); + reason.value = 'Posture'; + reason.dispatchEvent(new Event('input')); + await fixture.whenStable(); + element.querySelector('button[type=submit]')!.click(); + await fixture.whenStable(); + + const first = element.querySelector('.requests li'); + expect(first?.textContent).toContain('Laptop stand'); + expect(first?.textContent).toContain('Posture'); + }); +}); diff --git a/examples/enterprise/acme-portal/src/app/app.ts b/examples/enterprise/acme-portal/src/app/app.ts new file mode 100644 index 0000000..a5a61d6 --- /dev/null +++ b/examples/enterprise/acme-portal/src/app/app.ts @@ -0,0 +1,29 @@ +import { Component, signal } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { AcmeButton, AcmeCard } from '@acme/ui-core'; +import { AcmeTextField } from '@acme/ui-forms'; + +interface Request { + title: string; + reason: string; +} + +@Component({ + selector: 'app-root', + imports: [AcmeButton, AcmeCard, AcmeTextField, ReactiveFormsModule], + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App { + protected readonly form = new FormGroup({ + title: new FormControl('', { nonNullable: true, validators: Validators.required }), + reason: new FormControl('', { nonNullable: true }), + }); + protected readonly requests = signal([{ title: 'Second monitor', reason: 'Design reviews' }]); + + protected submit(): void { + if (this.form.invalid) return; + this.requests.update((list) => [this.form.getRawValue(), ...list]); + this.form.reset(); + } +} diff --git a/examples/enterprise/acme-portal/src/index.html b/examples/enterprise/acme-portal/src/index.html new file mode 100644 index 0000000..08d0340 --- /dev/null +++ b/examples/enterprise/acme-portal/src/index.html @@ -0,0 +1,13 @@ + + + + + AcmePortal + + + + + + + + diff --git a/examples/enterprise/acme-portal/src/main.ts b/examples/enterprise/acme-portal/src/main.ts new file mode 100644 index 0000000..5df75f9 --- /dev/null +++ b/examples/enterprise/acme-portal/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig) + .catch((err) => console.error(err)); diff --git a/examples/enterprise/acme-portal/src/styles.css b/examples/enterprise/acme-portal/src/styles.css new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/examples/enterprise/acme-portal/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/examples/enterprise/acme-portal/tsconfig.app.json b/examples/enterprise/acme-portal/tsconfig.app.json new file mode 100644 index 0000000..264f459 --- /dev/null +++ b/examples/enterprise/acme-portal/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.spec.ts" + ] +} diff --git a/examples/enterprise/acme-portal/tsconfig.json b/examples/enterprise/acme-portal/tsconfig.json new file mode 100644 index 0000000..2ab7442 --- /dev/null +++ b/examples/enterprise/acme-portal/tsconfig.json @@ -0,0 +1,33 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/examples/enterprise/acme-portal/tsconfig.spec.json b/examples/enterprise/acme-portal/tsconfig.spec.json new file mode 100644 index 0000000..d383706 --- /dev/null +++ b/examples/enterprise/acme-portal/tsconfig.spec.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "vitest/globals" + ] + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.spec.ts" + ] +} diff --git a/examples/enterprise/acme-ui/.editorconfig b/examples/enterprise/acme-ui/.editorconfig new file mode 100644 index 0000000..f166060 --- /dev/null +++ b/examples/enterprise/acme-ui/.editorconfig @@ -0,0 +1,17 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single +ij_typescript_use_double_quotes = false + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/examples/enterprise/acme-ui/.gitattributes b/examples/enterprise/acme-ui/.gitattributes new file mode 100644 index 0000000..ba64ad5 --- /dev/null +++ b/examples/enterprise/acme-ui/.gitattributes @@ -0,0 +1,2 @@ +# LF everywhere, so a Windows checkout and the dev container see the same files. +* text=auto eol=lf diff --git a/examples/enterprise/acme-ui/.gitignore b/examples/enterprise/acme-ui/.gitignore new file mode 100644 index 0000000..854acd5 --- /dev/null +++ b/examples/enterprise/acme-ui/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/examples/enterprise/acme-ui/.npmrc b/examples/enterprise/acme-ui/.npmrc new file mode 100644 index 0000000..6832165 --- /dev/null +++ b/examples/enterprise/acme-ui/.npmrc @@ -0,0 +1,2 @@ +# Acme packages come from the company registry, a local Verdaccio in this example. +@acme:registry=http://localhost:4873/ diff --git a/examples/enterprise/acme-ui/.prettierrc b/examples/enterprise/acme-ui/.prettierrc new file mode 100644 index 0000000..d6c16d7 --- /dev/null +++ b/examples/enterprise/acme-ui/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] +} diff --git a/examples/enterprise/acme-ui/README.md b/examples/enterprise/acme-ui/README.md new file mode 100644 index 0000000..15e836e --- /dev/null +++ b/examples/enterprise/acme-ui/README.md @@ -0,0 +1,7 @@ +# acme-ui + +Acme's UI libraries: `@acme/ui-core`, with buttons, cards and the design tokens in `theme.css`, and `@acme/ui-forms`, with form fields built on ui-core. They're published to the company registry that `.npmrc` names, and Acme's apps install them from there. + +- `pnpm run build` builds both, ui-core first, because ui-forms builds against it. +- `pnpm test` runs both libraries' tests. +- `pnpm run publish` publishes what `build` made. Only a release does this. diff --git a/examples/enterprise/acme-ui/angular.json b/examples/enterprise/acme-ui/angular.json new file mode 100644 index 0000000..448d6d3 --- /dev/null +++ b/examples/enterprise/acme-ui/angular.json @@ -0,0 +1,62 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "pnpm" + }, + "newProjectRoot": "projects", + "projects": { + "@acme/ui-core": { + "projectType": "library", + "root": "projects/acme/ui-core", + "sourceRoot": "projects/acme/ui-core/src", + "prefix": "acme", + "architect": { + "build": { + "builder": "@angular/build:ng-packagr", + "configurations": { + "production": { + "tsConfig": "projects/acme/ui-core/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "projects/acme/ui-core/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, + "test": { + "builder": "@angular/build:unit-test", + "options": { + "tsConfig": "projects/acme/ui-core/tsconfig.spec.json" + } + } + } + }, + "@acme/ui-forms": { + "projectType": "library", + "root": "projects/acme/ui-forms", + "sourceRoot": "projects/acme/ui-forms/src", + "prefix": "acme", + "architect": { + "build": { + "builder": "@angular/build:ng-packagr", + "configurations": { + "production": { + "tsConfig": "projects/acme/ui-forms/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "projects/acme/ui-forms/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, + "test": { + "builder": "@angular/build:unit-test", + "options": { + "tsConfig": "projects/acme/ui-forms/tsconfig.spec.json" + } + } + } + } + } +} diff --git a/examples/enterprise/acme-ui/package.json b/examples/enterprise/acme-ui/package.json new file mode 100644 index 0000000..2b9d2b2 --- /dev/null +++ b/examples/enterprise/acme-ui/package.json @@ -0,0 +1,32 @@ +{ + "name": "acme-ui", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "build": "ng build @acme/ui-core && ng build @acme/ui-forms", + "test": "ng build @acme/ui-core && ng test @acme/ui-core --watch=false && ng test @acme/ui-forms --watch=false", + "publish": "pnpm publish dist/acme/ui-core --no-git-checks && pnpm publish dist/acme/ui-forms --no-git-checks" + }, + "private": true, + "packageManager": "pnpm@12.16.1", + "dependencies": { + "@angular/common": "^21.2.0", + "@angular/compiler": "^21.2.0", + "@angular/core": "^21.2.0", + "@angular/forms": "^21.2.0", + "@angular/platform-browser": "^21.2.0", + "@angular/router": "^21.2.0", + "rxjs": "~7.8.0", + "tslib": "^2.3.0" + }, + "devDependencies": { + "@angular/build": "^21.2.29", + "@angular/cli": "^21.2.29", + "@angular/compiler-cli": "^21.2.0", + "jsdom": "^28.0.0", + "ng-packagr": "^21.2.0", + "prettier": "^3.8.1", + "typescript": "~5.9.2", + "vitest": "^4.0.8" + } +} diff --git a/examples/enterprise/acme-ui/pnpm-lock.yaml b/examples/enterprise/acme-ui/pnpm-lock.yaml new file mode 100644 index 0000000..c45ea0c --- /dev/null +++ b/examples/enterprise/acme-ui/pnpm-lock.yaml @@ -0,0 +1,6074 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.16.1 + version: 12.16.1 + +packages: + + '@pnpm/exe.android-arm64@12.16.1': + resolution: {integrity: sha512-PKf78Ew+i0CBYf0Xc+eMcqQiqyBT+u8XCVg1F5roc98/6wWfdYlWhrUMmEBiKWaJ3JqHxrtt8l1NwZ7qfJt2/Q==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.16.1': + resolution: {integrity: sha512-xdu7gMbB98pdvhwMeA6612Of515FDlRUtfA/1f7nWPZfPLnEdSRBO2u+1N0XBtQ/ennhPH/lrxB1gC6gnR/cdg==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.16.1': + resolution: {integrity: sha512-pVbmIIbhixiLvADLu1WA3TaW8FZy/D72R1De27WbbFx8/2mxvnsTyFVm4fO9kjpjMY7RzJd48CiQ96p8fO3ebQ==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.16.1': + resolution: {integrity: sha512-xZvQBFqcZjO7vFKTK3XsJpqY6lnBL9EWSLCzVUPq3kZ94+D96gwJCa76xxGNK8Cj32f4eeM91Qg+yNRR4sRpDw==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.16.1': + resolution: {integrity: sha512-zTtunfi9mBhRXiXEJYfwiJj0tk7MgTSfqDSRh+Az9FZ9THazVIiQiVNS8jpcsftDOqu7LKNffQdjmgwHDThfUg==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.16.1': + resolution: {integrity: sha512-BJ3mOmcmQgcUo92Ee/WfEOt1pRiv3y2MMAkhEiYHd85UiPpHTfFhv+SpCbALmfExzXk3UlaRtMYwHlkQymc4VA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.16.1': + resolution: {integrity: sha512-JaRd5gNxwdNGBBkrZFt8bknrK4YmHVJBW0JMhUQ0fJ0dEYp6C2GT9ExaCqOO8EwYEyqM+jpKgJIK+8WFaJy3ew==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.16.1': + resolution: {integrity: sha512-FnSV0Jv40S1cMv7vFNWwvu0X567Lec4L4HCRead3CFcD6zQDNbGw81s8N5bcKPahtPD9ApVuhsbmFCx4rw6RMw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.16.1': + resolution: {integrity: sha512-C1Sww4FN5IttD4atRRhAKmQtSGWVp04ZAKNxO8pfu2bkRPwOW+7v+2DH1Xs1W+kZYYpO86BYDvUcadefF+AgaQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.16.1': + resolution: {integrity: sha512-kn2fautsTcxIMODoovQVqZyfpDRdNgGkoeVqNdrKqbeBBvrEl+fXLkmZiq5xDht1LMr4sYiCNcsMfNMT6JPFmg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.16.1': + resolution: {integrity: sha512-PM6ccHCOe/O9SsTflTJTYWfwogJZa6EmUvpnwZgiPguJBddh20wDQKgxUPXf48rDsPm9vMsIij0uHswwwbXL8g==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.16.1': + resolution: {integrity: sha512-RK//8ZLcXGW8A2k8bQOXZDBN1Qu92zN4dYnG1bzj1zoQ1fkZ6jNzZ1AvXtsfGVma795CJtvoaF5bLXf4l/FIUw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.16.1': + resolution: {integrity: sha512-Ox3kNNVxtGw6pK7SVejmeOUgHhUTZgAM7NQQ6cU7frPw3ix9+j7d2MGbRg3fyEFAaKWq8ZAZLTQGcDyLnAu+qA==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.16.1': + resolution: {integrity: sha512-k+FhRJ0ttphFoFIVB1Sk4v76YJAulafmA/Ly57cysesUhhIzVlApeOShUz0jXubKx317TThlEfpQrukldnqORA==} + cpu: [x64] + os: [win32] + + pnpm@12.16.1: + resolution: {integrity: sha512-CR0d7nknV1Zhsl2rPCrjHK/BxU1rce1qZgDPDByxBDSkR+rhQBXxsj4ua+3u1535SpMuIWTSgyKA3OP7MvZW0Q==} + engines: {node: '>=18'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.16.1': + optional: true + + '@pnpm/exe.android-x64@12.16.1': + optional: true + + '@pnpm/exe.darwin-arm64@12.16.1': + optional: true + + '@pnpm/exe.darwin-x64@12.16.1': + optional: true + + '@pnpm/exe.freebsd-x64@12.16.1': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.16.1': + optional: true + + '@pnpm/exe.linux-arm64@12.16.1': + optional: true + + '@pnpm/exe.linux-ppc64@12.16.1': + optional: true + + '@pnpm/exe.linux-riscv64@12.16.1': + optional: true + + '@pnpm/exe.linux-s390x@12.16.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.16.1': + optional: true + + '@pnpm/exe.linux-x64@12.16.1': + optional: true + + '@pnpm/exe.win32-arm64@12.16.1': + optional: true + + '@pnpm/exe.win32-x64@12.16.1': + optional: true + + pnpm@12.16.1: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.16.1 + '@pnpm/exe.android-x64': 12.16.1 + '@pnpm/exe.darwin-arm64': 12.16.1 + '@pnpm/exe.darwin-x64': 12.16.1 + '@pnpm/exe.freebsd-x64': 12.16.1 + '@pnpm/exe.linux-arm64': 12.16.1 + '@pnpm/exe.linux-arm64-musl': 12.16.1 + '@pnpm/exe.linux-ppc64': 12.16.1 + '@pnpm/exe.linux-riscv64': 12.16.1 + '@pnpm/exe.linux-s390x': 12.16.1 + '@pnpm/exe.linux-x64': 12.16.1 + '@pnpm/exe.linux-x64-musl': 12.16.1 + '@pnpm/exe.win32-arm64': 12.16.1 + '@pnpm/exe.win32-x64': 12.16.1 + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@angular/common': + specifier: ^21.2.0 + version: 21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.0 + version: 21.2.26 + '@angular/core': + specifier: ^21.2.0 + version: 21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2) + '@angular/forms': + specifier: ^21.2.0 + version: 21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(@angular/platform-browser@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)))(rxjs@7.8.2) + '@angular/platform-browser': + specifier: ^21.2.0 + version: 21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)) + '@angular/router': + specifier: ^21.2.0 + version: 21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(@angular/platform-browser@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)))(rxjs@7.8.2) + rxjs: + specifier: ~7.8.0 + version: 7.8.2 + tslib: + specifier: ^2.3.0 + version: 2.9.1 + devDependencies: + '@angular/build': + specifier: ^21.2.29 + version: 21.2.29(@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3))(@angular/compiler@21.2.26)(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(@angular/platform-browser@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)))(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(chokidar@5.0.0)(less@4.9.1)(ng-packagr@21.2.7(@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3))(tslib@2.9.1)(typescript@5.9.3))(postcss@8.5.32)(tslib@2.9.1)(typescript@5.9.3)(vitest@4.1.12(jsdom@28.1.0)(vite@7.3.7(less@4.9.1)(sass@1.97.3))) + '@angular/cli': + specifier: ^21.2.29 + version: 21.2.29(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.0 + version: 21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3) + jsdom: + specifier: ^28.0.0 + version: 28.1.0 + ng-packagr: + specifier: ^21.2.0 + version: 21.2.7(@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3))(tslib@2.9.1)(typescript@5.9.3) + prettier: + specifier: ^3.8.1 + version: 3.9.10 + typescript: + specifier: ~5.9.2 + version: 5.9.3 + vitest: + specifier: ^4.0.8 + version: 4.1.12(jsdom@28.1.0)(vite@7.3.7(less@4.9.1)(sass@1.97.3)) + +packages: + + '@acemir/cssom@0.9.32': + resolution: {integrity: sha512-OijrAUu7rrJhSIoHNg6LMPS5e344nwUHA+1EFYYjrh64D+GmytUsfBQjci45TjT+rd5Spsp8B5xsZDT+TgyXXQ==} + + '@algolia/abtesting@1.14.1': + resolution: {integrity: sha512-Dkj0BgPiLAaim9sbQ97UKDFHJE/880wgStAM18U++NaJ/2Cws34J5731ovJifr6E3Pv4T2CqvMXf8qLCC417Ew==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-abtesting@5.48.1': + resolution: {integrity: sha512-LV5qCJdj+/m9I+Aj91o+glYszrzd7CX6NgKaYdTOj4+tUYfbS62pwYgUfZprYNayhkQpVFcrW8x8ZlIHpS23Vw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.48.1': + resolution: {integrity: sha512-/AVoMqHhPm14CcHq7mwB+bUJbfCv+jrxlNvRjXAuO+TQa+V37N8k1b0ijaRBPdmSjULMd8KtJbQyUyabXOu6Kg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.48.1': + resolution: {integrity: sha512-VXO+qu2Ep6ota28ktvBm3sG53wUHS2n7bgLWmce5jTskdlCD0/JrV4tnBm1l7qpla1CeoQb8D7ShFhad+UoSOw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.48.1': + resolution: {integrity: sha512-zl+Qyb0nLg+Y5YvKp1Ij+u9OaPaKg2/EPzTwKNiVyOHnQJlFxmXyUZL1EInczAZsEY8hVpPCLtNfhMhfxluXKQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.48.1': + resolution: {integrity: sha512-r89Qf9Oo9mKWQXumRu/1LtvVJAmEDpn8mHZMc485pRfQUMAwSSrsnaw1tQ3sszqzEgAr1c7rw6fjBI+zrAXTOw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.48.1': + resolution: {integrity: sha512-TPKNPKfghKG/bMSc7mQYD9HxHRUkBZA4q1PEmHgICaSeHQscGqL4wBrKkhfPlDV1uYBKW02pbFMUhsOt7p4ZpA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.48.1': + resolution: {integrity: sha512-4Fu7dnzQyQmMFknYwTiN/HxPbH4DyxvQ1m+IxpPp5oslOgz8m6PG5qhiGbqJzH4HiT1I58ecDiCAC716UyVA8Q==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.48.1': + resolution: {integrity: sha512-/RFq3TqtXDUUawwic/A9xylA2P3LDMO8dNhphHAUOU51b1ZLHrmZ6YYJm3df1APz7xLY1aht6okCQf+/vmrV9w==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.48.1': + resolution: {integrity: sha512-Of0jTeAZRyRhC7XzDSjJef0aBkgRcvRAaw0ooYRlOw57APii7lZdq+layuNdeL72BRq1snaJhoMMwkmLIpJScw==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.48.1': + resolution: {integrity: sha512-bE7JcpFXzxF5zHwj/vkl2eiCBvyR1zQ7aoUdO+GDXxGp0DGw7nI0p8Xj6u8VmRQ+RDuPcICFQcCwRIJT5tDJFw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.48.1': + resolution: {integrity: sha512-MK3wZ2koLDnvH/AmqIF1EKbJlhRS5j74OZGkLpxI4rYvNi9Jn/C7vb5DytBnQ4KUWts7QsmbdwHkxY5txQHXVw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.48.1': + resolution: {integrity: sha512-2oDT43Y5HWRSIQMPQI4tA/W+TN/N2tjggZCUsqQV440kxzzoPGsvv9QP1GhQ4CoDa+yn6ygUsGp6Dr+a9sPPSg==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.48.1': + resolution: {integrity: sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==} + engines: {node: '>= 14.0.0'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@angular-devkit/architect@0.2102.29': + resolution: {integrity: sha512-o7dg7tlGbVr/ddXSAD9B/ozNwETfI7hzaBAe4v00+L+Y7Mz85hIy3R9/B0elY/SyoEDkW/HHTRsLubHMWg2AZw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/core@21.2.29': + resolution: {integrity: sha512-807H6zbmVxoTZEZymJM/YKcn9BcHQsgCQg9Yki4jDAQIMockyIN9F7jscxHqBtLt2rXHiiDVyWiJhMEyot1OXQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^5.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics@21.2.29': + resolution: {integrity: sha512-Bx/my36Rxwf0ptgwngXTCfbOPHyr7FX2PL3mBuaZh99065969leFd6dFajss9hSAoNreZtJu73dw5AjOpgIjBw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@angular/build@21.2.29': + resolution: {integrity: sha512-aahiMmyvY10nYaduYqrJPgHY3k4FpVqWYzaruxLWF74vRhz8x3OOX1rdro1OHn2p2C2/Ga80kAZ3hlsx3OyGFQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + '@angular/compiler': ^21.0.0 + '@angular/compiler-cli': ^21.0.0 + '@angular/core': ^21.0.0 + '@angular/localize': ^21.0.0 + '@angular/platform-browser': ^21.0.0 + '@angular/platform-server': ^21.0.0 + '@angular/service-worker': ^21.0.0 + '@angular/ssr': ^21.2.29 + karma: ^6.4.0 + less: ^4.2.0 + ng-packagr: ^21.0.0 + postcss: ^8.4.0 + tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 + tslib: ^2.3.0 + typescript: '>=5.9 <6.0' + vitest: ^4.0.8 + peerDependenciesMeta: + '@angular/core': + optional: true + '@angular/localize': + optional: true + '@angular/platform-browser': + optional: true + '@angular/platform-server': + optional: true + '@angular/service-worker': + optional: true + '@angular/ssr': + optional: true + karma: + optional: true + less: + optional: true + ng-packagr: + optional: true + postcss: + optional: true + tailwindcss: + optional: true + vitest: + optional: true + + '@angular/cli@21.2.29': + resolution: {integrity: sha512-RGi3JzLgrCb5tZVqqCPjbb1w/1XFwFnno9iIHMs1wcecxcEhiXLq/klLj4wmHk4iWkoCYaltYs+h9fCmkvrVzA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular/common@21.2.26': + resolution: {integrity: sha512-2lnNdImIHaeTmYqW+lEKeU9vPauNLz8NgM2MoZs7kV9D+CfPMi90F5VwRg0q16J/UiKS8uiYIYmUeaoo2wu4Pg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/core': 21.2.26 + rxjs: ^6.5.3 || ^7.4.0 + + '@angular/compiler-cli@21.2.26': + resolution: {integrity: sha512-W/S9tImJg9IqEQmkP1yPY2LD4D6/2uSMvg2eHZuTg8zOq3YrhzJINlCKpRy/wp9oWsKxlDKef6tddMDiNa32Hw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@angular/compiler': 21.2.26 + typescript: '>=5.9 <6.1' + peerDependenciesMeta: + typescript: + optional: true + + '@angular/compiler@21.2.26': + resolution: {integrity: sha512-awrZjb8ok4TE871RVCvVkPfO63vmW0whqslXQ1s5sSFzlYoYDGxQznVbp0PoDiYU+QpoxMK50Lec/J2l0+0fjw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@angular/core@21.2.26': + resolution: {integrity: sha512-EXALAnHRhhBj9WAYend5IIpFlpqPq+HM3OrHxBiWNQoqxOfFKnmRKGERLlX3FEDSzWQt+iZcZHpd2569Xo0pMA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/compiler': 21.2.26 + rxjs: ^6.5.3 || ^7.4.0 + zone.js: ~0.15.0 || ~0.16.0 + peerDependenciesMeta: + '@angular/compiler': + optional: true + zone.js: + optional: true + + '@angular/forms@21.2.26': + resolution: {integrity: sha512-FnQ8LQu5liO5afYaCeRz0TMJW82oiEWo9h7g/KrvjCsgCHW9kY7uLFc3WxFCppVycl0kZLJtYXprkkRIJfMsbw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/common': 21.2.26 + '@angular/core': 21.2.26 + '@angular/platform-browser': 21.2.26 + rxjs: ^6.5.3 || ^7.4.0 + + '@angular/platform-browser@21.2.26': + resolution: {integrity: sha512-0yJN5FZX88SqdAnsmZC5E0MGpkQyG4CXjdWN3wTX3vFMsnetvm9MqyZKalxNFneucXV0KLrb8oRma5San3Hmeg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/animations': 21.2.26 + '@angular/common': 21.2.26 + '@angular/core': 21.2.26 + peerDependenciesMeta: + '@angular/animations': + optional: true + + '@angular/router@21.2.26': + resolution: {integrity: sha512-GSaSVW1y6SxKVAseszFzzREhxRnFbVtjE4w3acQpBg1SjgKflFrJ87U1vh39GagSfi8n0N8sswdZym4jHxet9w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/common': 21.2.26 + '@angular/core': 21.2.26 + '@angular/platform-browser': 21.2.26 + rxjs: ^6.5.3 || ^7.4.0 + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.13': + resolution: {integrity: sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.13': + resolution: {integrity: sha512-8WxJ4/Vmes3ghacj8VOJy3NW8eyHiFY/4thU/LSBuBE470+igjR/j1ZTNn8fV5VdNrLx6iXUwig526UdOvfP0w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.13': + resolution: {integrity: sha512-/U96wOz+SCoZ7Da/8kjDGltcNY280JPQcRNPK0Hk3PTbdpaiBDQImtC6JsbVwb1HGMr3usZQU2WsWVjhYNmRzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.13': + resolution: {integrity: sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.13': + resolution: {integrity: sha512-laoRpD/kO/iA4CDlgJbixHekg7oPvWALK0PQyK3Ob2I2njZRvVAD77RfWCIImtqWCq6KijoBoZK7bp/HlTWASQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.13': + resolution: {integrity: sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.13': + resolution: {integrity: sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.11': + resolution: {integrity: sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==} + engines: {node: '>=6.9.0'} + + '@bramus/specificity@2.4.3': + resolution: {integrity: sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==} + hasBin: true + + '@csstools/color-helpers@6.1.2': + resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.4.3': + resolution: {integrity: sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-color-parser@4.2.7': + resolution: {integrity: sha512-zgm9yjQz5iJB4VL8RyVwd71TWB8Q89FSxt/NzXoWqPSkEBOVtn9L6vAPtSc9hpKfB0Awo/AL2VKFHjYetBLFHQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-parser-algorithms@4.0.2': + resolution: {integrity: sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-syntax-patches-for-csstree@1.1.16': + resolution: {integrity: sha512-LsDbJPP/B8cPasUJyTA5S99F7C4G93ngW8hUluXz8CFeOaaRnBPtBiIlvgcPYU0HmHNz8CXg+1vwFrIB4F6Dgw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.2': + resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} + engines: {node: '>=20.19.0'} + + '@emnapi/core@1.11.3': + resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.3': + resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@exodus/bytes@1.16.0': + resolution: {integrity: sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@gar/promise-retry@1.0.3': + resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@harperfast/extended-iterable@1.0.5': + resolution: {integrity: sha512-4ScXlphaKEa49RpHmqwATq9QI9aUTdWVYDlJkzxhsD7ShJP+pHCYLznMO9n/wREY8jPjRZ59wLJY6HM5yB5Gkw==} + + '@hono/node-server@2.1.6': + resolution: {integrity: sha512-p/KH9MnfNcsm3sR6x4lNgm5j+QN4Et8srKredl9DZfXiyZ1UQ3aQ2YVpFLbqktYN5JI+DB+VZFKM2PqE5hLKjQ==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 || ^5.0.0-0 + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@listr2/prompt-adapter-inquirer@3.0.5': + resolution: {integrity: sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@inquirer/prompts': '>= 3 < 8' + listr2: 9.0.5 + + '@lmdb/lmdb-darwin-arm64@3.5.1': + resolution: {integrity: sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==} + cpu: [arm64] + os: [darwin] + + '@lmdb/lmdb-darwin-x64@3.5.1': + resolution: {integrity: sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==} + cpu: [x64] + os: [darwin] + + '@lmdb/lmdb-linux-arm64@3.5.1': + resolution: {integrity: sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==} + cpu: [arm64] + os: [linux] + + '@lmdb/lmdb-linux-arm@3.5.1': + resolution: {integrity: sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==} + cpu: [arm] + os: [linux] + + '@lmdb/lmdb-linux-x64@3.5.1': + resolution: {integrity: sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==} + cpu: [x64] + os: [linux] + + '@lmdb/lmdb-win32-arm64@3.5.1': + resolution: {integrity: sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==} + cpu: [arm64] + os: [win32] + + '@lmdb/lmdb-win32-x64@3.5.1': + resolution: {integrity: sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==} + cpu: [x64] + os: [win32] + + '@modelcontextprotocol/sdk@1.32.1': + resolution: {integrity: sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.7': + resolution: {integrity: sha512-2zPHLn+MuGJ6GP/5jDrwa9Jy0CI+D3Ibpvopf5SFfmc1G9gdofR4uB7YrJAAp4vodn/7iDOciyv8bgkRPLw7cA==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.7': + resolution: {integrity: sha512-of1miwFfEdgs5nythewsEoocfpbjzUf8PC996YDe3kAyv0ZJiMJs5NXlYhE6IdjxfNEPqUXj/BYBnpD8kRdfeQ==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.7': + resolution: {integrity: sha512-jcRydYARTW0gX5idJhxV7m3KkRUD7KtZLYFF1ZY18HrPqpGY0TCqPUTJCwka2kVfVOwfKs3ZmlPSmQb4S6xj7w==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.7': + resolution: {integrity: sha512-aDPbiEkFHs3H68705JKxJ15IT41wFdvDhba1pL8ygfdoshboL5dsXtiu76/CqhLWYneE+9hwBE+0abqNOiQ4Bw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.7': + resolution: {integrity: sha512-A9xOmARctmUvHrBj5GGzGAigsZFnqE4kld5oprX3PyPbkVALV4jwBc17gPZrhSOvnXQc81hE9K7S2hDUCgialA==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.7': + resolution: {integrity: sha512-PRBiOqK4mcR0rvm6nybO7pSOYRR2Zza0+43UK2IvbVGelzMqZViiZJmOWGuWOX678QKESO6YrHx36z43nSS6iQ==} + cpu: [x64] + os: [win32] + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-android-arm-eabi@1.1.1': + resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/nice-android-arm64@1.1.1': + resolution: {integrity: sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/nice-darwin-arm64@1.1.1': + resolution: {integrity: sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/nice-darwin-x64@1.1.1': + resolution: {integrity: sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/nice-freebsd-x64@1.1.1': + resolution: {integrity: sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + resolution: {integrity: sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + resolution: {integrity: sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-musl@1.1.1': + resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-openharmony-arm64@1.1.1': + resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [openharmony] + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + resolution: {integrity: sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + resolution: {integrity: sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + resolution: {integrity: sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/nice@1.1.1': + resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} + engines: {node: '>= 10'} + + '@napi-rs/wasm-runtime@1.2.7': + resolution: {integrity: sha512-zJccwplOAbgfZju95IUHazLnx9RAPtPN2YGJXI6UbrgeaR9QfPjGrQL6ERkSAtnVCA4Q+83ApGyaX5zdpCaKkQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || 2.0.0-beta.2 || 2.0.0-beta.3 + '@emnapi/runtime': ^1.7.1 || 2.0.0-beta.2 || 2.0.0-beta.3 + + '@npmcli/agent@4.0.2': + resolution: {integrity: sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/fs@5.0.0': + resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/git@7.0.2': + resolution: {integrity: sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/installed-package-contents@4.0.0': + resolution: {integrity: sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + '@npmcli/node-gyp@5.0.0': + resolution: {integrity: sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/package-json@7.0.5': + resolution: {integrity: sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/promise-spawn@9.0.2': + resolution: {integrity: sha512-h7b1j0egrruBm/32eVA6/zMw5VJxxaB3vT+uVGGQ4SAAMrQVFHNuzL4+EAXn66j+huj0jApINc1fW3fC+kVoeA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/redact@4.0.0': + resolution: {integrity: sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/run-script@10.0.4': + resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@oxc-project/types@0.113.0': + resolution: {integrity: sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==} + + '@parcel/watcher-android-arm64@2.6.0': + resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.6.0': + resolution: {integrity: sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.6.0': + resolution: {integrity: sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.6.0': + resolution: {integrity: sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.6.0': + resolution: {integrity: sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.6.0': + resolution: {integrity: sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + resolution: {integrity: sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.6.0': + resolution: {integrity: sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.6.0': + resolution: {integrity: sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.6.0': + resolution: {integrity: sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.6.0': + resolution: {integrity: sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-x64@2.6.0': + resolution: {integrity: sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.6.0': + resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} + engines: {node: '>= 10.0.0'} + + '@rolldown/binding-android-arm64@1.0.0-rc.4': + resolution: {integrity: sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.4': + resolution: {integrity: sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.4': + resolution: {integrity: sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.4': + resolution: {integrity: sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': + resolution: {integrity: sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': + resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': + resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': + resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': + resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': + resolution: {integrity: sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.4': + resolution: {integrity: sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': + resolution: {integrity: sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': + resolution: {integrity: sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.4': + resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==} + + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.5.1': + resolution: {integrity: sha512-P9ZvnfouRu4MSC+ilERaw6FmZs8zqKhpHxEs4aGDGWQAMzQGXnhvCSvoNwokfNvdt7t3Y5Y4p6gStQgezeIqug==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.68.0': + resolution: {integrity: sha512-Ta2n0ntLfzP0LqwwcXOIh2z0mnHjlxMS+h4Y9lo+w33Quyf9KFKEi9QZ2IKqeEBKr0qxNpXc/DQjz5KVIs8AEA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.68.0': + resolution: {integrity: sha512-iFDtS0oMaBvKbtrRKXmRcBSsI0UaWL2bWpd62iHzb0A8RSPrVz47mUGE0CfrrD0c/7/7dUhNtoN3cc6aEUn1Ow==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-android-x64@4.68.0': + resolution: {integrity: sha512-OnYETr+cZYSbesBD1tCvUjrBUTkjSaOcewRUoIHqD0XI3yn5i9WpdFaE3LZwrMlq0yeJNU0N5sxfHK/fNVMqlQ==} + cpu: [x64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.68.0': + resolution: {integrity: sha512-2qavWZtFeon/6oMkjsBF3OH7ENKgRTW4xRTwmz9VJeNUYhmPitpR9Jd4sAAc2OZJryHs2neoovMTCYnNvhvANQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.68.0': + resolution: {integrity: sha512-QpgJO+GwNQDPt6uYcFSjZTFVyHNIeumN2am7RISDR9Nf3e/h8sur9cIfplPGihZPUn+sTw9j3JErZIUkHg8+Pw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.68.0': + resolution: {integrity: sha512-TERqEn6P9bp4lsTyoFjT3nLKV6G++76HhGb4k6dRGcs+jvhVcTGaS2zkeRdLLRjwKxpBtdCZe11kUQ2hNGgFSA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.68.0': + resolution: {integrity: sha512-RBjDrqb7h5l6Xgdgz5eKYvbpnUkii/z5ejWuZmHJsXORi0X0tTtFzEEgJj2DX995kW18c6T9LGFn/deEAMXDcQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.68.0': + resolution: {integrity: sha512-HtLgXBqjF1Ampt9eJjzSmoGDvNb4LP8Y2aPXQZeQ5qhyExXLmoXJB9Jfm/nPi/8fJ3UFn4sZ7pQKyr8V+oTN8g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.68.0': + resolution: {integrity: sha512-q51xJyqjaqCNtDjYD7xNVwsPa2TcVfAIBeP6E20wicKZ6XYp8Taz9SUPYhIENojAQIAoV7cO3mUdMUgWYEAuhQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.68.0': + resolution: {integrity: sha512-wejnVabl92oFC/o4sr/EEJuJZMHtMq4C+LBIPphN4EUiVHe9qvZNw+biqhVHpX2ggs1+Z3ugXGwJxT+qhyaaYQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.68.0': + resolution: {integrity: sha512-tyu+hrsCWHHqV6+2tligV/recJH+HVvNV2Sp4nS1C+MKQPkNXRovKIzyHy0V0xXjm6igCLgsT2vTky5CvWP37g==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.68.0': + resolution: {integrity: sha512-5gjJNglKDfgs3OmnGi/RVZQPhEfRoCIHxwSAP9ngmdnxreu0Vl9lI4gxmFBXOhKdftpS82xRdc/7Dp5/A1F0qg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.68.0': + resolution: {integrity: sha512-2IBxTUds33rFYwOIEyf77wfW8WQThwiQQ1ofXSraZQG7PiIq7BnQBf/ge0qEI/Ny/2cSc7SKkGO/wUDqmYXqTg==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.68.0': + resolution: {integrity: sha512-mE/Pj2PPTZIVHkNNuHq+eYoU3td8LCm73zjrXoBWkNlnuULJS2l/fxwgGPpN4WUCNZgKZ1IXwrnnETPyOcWPmg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.68.0': + resolution: {integrity: sha512-dYuykUkAiTv+RQdP3Ib47XcFiImkBWiRpTGSQWAMfZPQyoQYGe8Je4IUeZYNUQWIojmRCUnt5xsH4BbUb2uqSw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.68.0': + resolution: {integrity: sha512-OnPDDFXLqJewY56T6ow8sA4wuzbqlliD7QJgkrx2KX+pWOpjI3usYXgB0pwK3nsgTF2odCw9IuJs0Uis/jy2qA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.68.0': + resolution: {integrity: sha512-CqoQf06cK0brrRfDqH7Mt8G4MdteFGbjBdC61zEMaSVna6xo6QMUixa9bAkAD4a92g3ElPbQAkLMUzdf+Lk8dg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.68.0': + resolution: {integrity: sha512-LPHNmE8GwiyELGLUTkqj4zsP1YEHzGdtwnKxAcdP/swsNxXsfxPtpe5nrD/KGxr136xPB+eWv2r/SGj9md0pnQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.68.0': + resolution: {integrity: sha512-GDOmvgephpRCNyNGRQOZh0KD2gTyRVM1Bb1iOo759xeCUpCAoRCmJioOOSu2MwyWfNOocGd0V5I9vYLfteC6/A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.68.0': + resolution: {integrity: sha512-HADRxkvl5eg3hbemCmv81UwTfdQy/CiSJDOabLE+p5tpm3OTjuRtzpLXy5zPfGY9FQD4Qv7qHptXmG/YjmNmyw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.68.0': + resolution: {integrity: sha512-iERrpqu1VEfyX736HLKiXRbwGDkCV/iybARY/mn14KcI6oKhJGBx1e+bU+g+umTKghtiGSPaHkwwpII4/N3RjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.68.0': + resolution: {integrity: sha512-hBn57IqAbpXpyktckonwePTaFOQS6THph8ZnpRO3eZHY4LncuZGjSsseigBnKuSMKKpLnJJ7PemDvXEzniXC+A==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.68.0': + resolution: {integrity: sha512-pJWb7mIw9Z25Hl3Q6nHFjl7AnczfOpm2gbOFYguSRv6TiH7DzsoAk/thymb/URSQu9OUEK7uaDMC5Xfm+7TAww==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.68.0': + resolution: {integrity: sha512-pnrhmoyF2otH2Mezol14/ELUFGuRddOCz6+I+J0pq2Itnp4UxkjABQcXawCNMaKNsCRiuL90BtNzbEd0fHjpIg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.68.0': + resolution: {integrity: sha512-1dJ3U+ktdtaIdPaOZKwIX57+bKQk4iAjs5M1wQWLKRB6Wglp98qf8LUeuN4u6bGlIYouZKaySZJ5Qd4OalUMnA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.68.0': + resolution: {integrity: sha512-e/NRJFl48wL5DFNSzQrvm4kaB5dzNNmqWjIJ6And2Sjuw4klO3w3TXMvCAEF1tHTrQQgpOD4QVkBE/92/yBGrQ==} + cpu: [x64] + os: [win32] + + '@rollup/wasm-node@4.68.0': + resolution: {integrity: sha512-lv6TzXKQKKQ1cWnQBU1a0liwm+IvSVQ2lCUXo4p1NqATGw+aBnVpZmi/cW1CNF7OJ/3EAdSlbEgnJwcsdWtADw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + '@schematics/angular@21.2.29': + resolution: {integrity: sha512-u8WDSr+AcOo+IwfS4gXVB2jT+j8EhW8P7+zoYzaBywnQl4zYLsPalo0pB8I4tbsmPyZKIMrHmlZ3b6C3UYkvUA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@sigstore/bundle@4.0.0': + resolution: {integrity: sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/core@3.2.1': + resolution: {integrity: sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/protobuf-specs@0.5.2': + resolution: {integrity: sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/sign@4.1.1': + resolution: {integrity: sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/tuf@4.0.2': + resolution: {integrity: sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/verify@3.1.1': + resolution: {integrity: sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tufjs/canonical-json@2.0.0': + resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@tufjs/models@4.1.0': + resolution: {integrity: sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.10': + resolution: {integrity: sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==} + + '@vitejs/plugin-basic-ssl@2.1.4': + resolution: {integrity: sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 + + '@vitest/expect@4.1.12': + resolution: {integrity: sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==} + + '@vitest/mocker@4.1.12': + resolution: {integrity: sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.12': + resolution: {integrity: sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==} + + '@vitest/runner@4.1.12': + resolution: {integrity: sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==} + + '@vitest/snapshot@4.1.12': + resolution: {integrity: sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==} + + '@vitest/spy@4.1.12': + resolution: {integrity: sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==} + + '@vitest/utils@4.1.12': + resolution: {integrity: sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==} + + '@yarnpkg/lockfile@1.1.0': + resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + algoliasearch@5.48.1: + resolution: {integrity: sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==} + engines: {node: '>= 14.0.0'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.5.0: + resolution: {integrity: sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.11.35: + resolution: {integrity: sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + beasties@0.4.1: + resolution: {integrity: sha512-2Imdcw3LznDuxAbJM26RHniOLAzE6WgrK8OuvVXCQtNBS8rsnD9zsSEa3fHl4hHpUY7BYTlrpvtPVbvu9G6neg==} + engines: {node: '>=18.0.0'} + + bidi-js@1.1.0: + resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==} + + body-parser@2.4.0: + resolution: {integrity: sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==} + engines: {node: '>=18'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + browserslist@4.29.5: + resolution: {integrity: sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cacache@20.0.4: + resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==} + engines: {node: ^20.17.0 || >=22.9.0} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001823: + resolution: {integrity: sha512-TQ5s/djDjNk1axreUW+4rULH+BmdCMLiJkhaXnO5wqivG7tITkSUj4ItIdzqdZHKaXcHAV8dhH3JxOM7/7bwqw==} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + common-path-prefix@3.0.0: + resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@6.0.0: + resolution: {integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@7.0.0: + resolution: {integrity: sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==} + engines: {node: '>= 6'} + + cssstyle@6.2.0: + resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} + engines: {node: '>=20'} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dependency-graph@1.0.0: + resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==} + engines: {node: '>=4'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.478: + resolution: {integrity: sha512-pftG+ma5Lm8O6/p45bhYT6StBN5ziQymaRzmCZwFl8DwWHKPPD93FdOmP1aqftcepolQYvrcyTLa17kE8Yf+jg==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@2.0.1: + resolution: {integrity: sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==} + engines: {node: '>= 0.8'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + entities@9.0.0: + resolution: {integrity: sha512-oJFNDJg3kTPwAD1cNn2+WmUq4wFoo09npc3Ydf1D7ED4smRiWrsKnaLxykrTWgyGEcusc87P5dxlAleol9kpkg==} + engines: {node: '>=20.19.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + express-rate-limit@8.7.3: + resolution: {integrity: sha512-zPLqDI2f0gV2WmGnacLf9XSSqn4VldRyP5LCzOwx0BKH40mO6+2PCFkoSsRO2jePx87/Kf6NUF6P2QOTKXHbQA==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.3.0: + resolution: {integrity: sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-cache-directory@6.0.0: + resolution: {integrity: sha512-CvFd5ivA6HcSHbD+59P7CyzINHXzwhuQK8RY7CxJZtgDSAtRlHiCaQpZQ2lMR/WRyUIEmzUvL6G2AGurMfegZA==} + engines: {node: '>=20'} + + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@13.0.7: + resolution: {integrity: sha512-wfsFgXQ8A3SEI9XGf7Nl3Y9l7ffSrGh7SUCT7vU/2CzYPep9kjxAn1S40F9OPKxAMGCVl7HtNlJhrCJUeUq83g==} + engines: {node: 18 || 20 || >=22} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hono@4.13.17: + resolution: {integrity: sha512-vZIcbkvjOR5hHRWERbGkr8fGfF0GGM1ooYkuH448iO3b2AdVEr3AXZSw4Z3Kn+1L5z0CVoVeyV29VGYmFh05kQ==} + engines: {node: '>=16.9.0'} + + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + + http-cache-semantics@4.3.0: + resolution: {integrity: sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ignore-walk@8.0.0: + resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==} + engines: {node: ^20.17.0 || >=22.9.0} + + immutable@5.1.9: + resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + injection-js@2.6.1: + resolution: {integrity: sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==} + + ip-address@10.7.6: + resolution: {integrity: sha512-00LYkM6byILa9eQIx6qoaXCM40xTLd56AZ4vyaPW3YTyb49VZYPo5xsiAq4tuQ54GH4R7gVdYScJ1B7B77PuTg==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + jose@6.2.15: + resolution: {integrity: sha512-qdF6ewcZARtef63fJ9AKKZPgxw+t7USH6HLXYcD4CrdmSvDp79rpJSPFEKc90QBeGS63u1KQtN2MZQHC0yDIzA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@5.0.0: + resolution: {integrity: sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + + less@4.9.1: + resolution: {integrity: sha512-orp15PfJvvNDIqJdVWzMI9Sjpjp3VTiw3sfvbB+67LlISTEn8uVT2EdYSuyl02BLvaftv6sdk9Umnxmm5rckmg==} + engines: {node: '>=18'} + hasBin: true + + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + + lmdb@3.5.1: + resolution: {integrity: sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==} + hasBin: true + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@5.1.0: + resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==} + engines: {node: '>=18'} + + make-fetch-happen@15.0.6: + resolution: {integrity: sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==} + engines: {node: ^20.17.0 || >=22.9.0} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@5.0.2: + resolution: {integrity: sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msgpackr-extract@3.0.7: + resolution: {integrity: sha512-/dOQboWnkR1cjm88XyH/cGSivECGgyQu3814+bNUAq5wACQC8Ya/CViB9WApvWJ5Hya/JJQQmUeBFOZj8MLjGA==} + hasBin: true + + msgpackr@1.12.1: + resolution: {integrity: sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ==} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + needle@2.9.1: + resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==} + engines: {node: '>= 4.4.x'} + hasBin: true + + needle@3.5.0: + resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==} + engines: {node: '>= 4.4.x'} + hasBin: true + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + ng-packagr@21.2.7: + resolution: {integrity: sha512-G1LWptU48IbGQcAqW77JfPpGwga5dI+XUOsyBDD1zPuA1QeLqnQTPqV4tSkbL8WZJwwmqgttRrL89DD35p8vuQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@angular/compiler-cli': ^21.0.0 || ^21.2.0-next + tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 + tslib: ^2.3.0 + typescript: '>=5.9 <6.0' + peerDependenciesMeta: + tailwindcss: + optional: true + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + + node-gyp@12.4.0: + resolution: {integrity: sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + node-releases@2.0.63: + resolution: {integrity: sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==} + engines: {node: '>=18'} + + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + npm-bundled@5.0.0: + resolution: {integrity: sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-install-checks@8.0.0: + resolution: {integrity: sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-normalize-package-bin@5.0.0: + resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-package-arg@13.0.2: + resolution: {integrity: sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-packlist@10.0.4: + resolution: {integrity: sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-pick-manifest@11.0.3: + resolution: {integrity: sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-registry-fetch@19.1.1: + resolution: {integrity: sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==} + engines: {node: ^20.17.0 || >=22.9.0} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + on-finished@2.4.2: + resolution: {integrity: sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + ora@9.3.0: + resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} + engines: {node: '>=20'} + + ora@9.4.1: + resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==} + engines: {node: '>=20'} + + ordered-binary@1.6.3: + resolution: {integrity: sha512-Bc8LnA2x4ZHRQ9LjCm/NOdD3+CTQPhGNu8m7/6+ERtiB6Bb/fn7nCySwcmAC+5WCqwelpSAQgo1VX9BGpsC0tQ==} + + p-map@7.1.1: + resolution: {integrity: sha512-6Lh3jQPLthYujSrlw6ccWXBGZTkoTCz27Hd92g79LH/kjAbgoEu0t442cCCNbegPSJsA0lwDX8z0bcKtkpA32g==} + engines: {node: '>=18'} + + pacote@21.5.1: + resolution: {integrity: sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + + parse5-html-rewriting-stream@8.0.0: + resolution: {integrity: sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==} + + parse5-sax-parser@8.0.0: + resolution: {integrity: sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==} + + parse5@8.1.0: + resolution: {integrity: sha512-sq3qBFpee5jeocbFbO48VHlGr8sT7mbXS0vxlKN6sottBtJzrOG3998sr0llSZcQAGgfpdmEYAIZE0P9z0dE8w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + piscina@5.3.2: + resolution: {integrity: sha512-vv7l/mM7B9WgrhDaudIqv1x6v4LOBRYYmpWGdWushhpbCmVHp11sUB2v4hj1CAeTGXVxuZFz6xOU1RalAiPKfQ==} + engines: {node: '>=20.x'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + pkg-dir@8.0.0: + resolution: {integrity: sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==} + engines: {node: '>=18'} + + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + + postcss-safe-parser@7.1.0: + resolution: {integrity: sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss@8.5.32: + resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.9.10: + resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} + engines: {node: '>=14'} + hasBin: true + + probe-image-size@7.5.0: + resolution: {integrity: sha512-ietdx4XPgmnzHRZOq1/KtcnsBkL4bRsBe+Szj5Tuq1/KPqDG42OspG1IhJb7vz6Z9pSzhp0J6UPnw6hujJTWoQ==} + + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + proxy-addr@2.0.8: + resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} + engines: {node: '>= 0.10'} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.17.1: + resolution: {integrity: sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==} + engines: {node: '>=0.6'} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rolldown@1.0.0-rc.4: + resolution: {integrity: sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup-plugin-dts@6.4.1: + resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==} + engines: {node: '>=20'} + peerDependencies: + rollup: ^3.29.4 || ^4 + typescript: ^4.5 || ^5.0 || ^6.0 + + rollup@4.68.0: + resolution: {integrity: sha512-BIHWnc1Qjdy+WLfOdcSd67sxO4iW6pTnhdZosg+7ywSaiMWxxzKldkkNpUJqeXYAiJzyB14P2cIjqC6tlb6xzw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass@1.108.1: + resolution: {integrity: sha512-sIYgSvOPeD9wABwDS/ofNjHASSck+7h3jLkk7wG1NPFL3XeS6vOQjBlAFSmRV09kZr1yt/Tuayt6kKtBbLjHNQ==} + engines: {node: '>=20.19.0'} + hasBin: true + + sass@1.97.3: + resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==} + engines: {node: '>=14.0.0'} + hasBin: true + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@6.3.2: + resolution: {integrity: sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.6: + resolution: {integrity: sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sigstore@4.1.1: + resolution: {integrity: sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==} + engines: {node: ^20.17.0 || >=22.9.0} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.10: + resolution: {integrity: sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.24: + resolution: {integrity: sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==} + + ssri@13.0.1: + resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + stdin-discarder@0.3.2: + resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + engines: {node: '>=18'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} + engines: {node: '>=20'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tar@7.5.22: + resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==} + engines: {node: '>=18'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.20: + resolution: {integrity: sha512-C630TWXZCZmLq0F2bW4QqwnaaxCQ/svpAqvJwnC4JvgEM5ohhBnGsEzTLSOXcN+xjz+kEaHS5uoWIw2klnqd0A==} + + tldts@7.4.19: + resolution: {integrity: sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q==} + hasBin: true + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tough-cookie@6.1.0: + resolution: {integrity: sha512-FTxc8u8Kl2tz0Lat1u8ksvcy8bwHRyEMawwTWybo51JqaC6O7SvfuhAKg+Ld6VqyIe2QRJBzY9HA7y2WyFRT2w==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + tslib@2.9.1: + resolution: {integrity: sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==} + + tuf-js@4.1.0: + resolution: {integrity: sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici@6.29.1: + resolution: {integrity: sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==} + engines: {node: '>=18.17'} + + undici@7.30.1: + resolution: {integrity: sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==} + engines: {node: '>=20.18.1'} + + undici@7.30.2: + resolution: {integrity: sha512-mLyPQnHwUxaM/rideoK/+KL8DaAJLRl4aBCfmX444aCSLnIaomqPLtNRe2jdCYcsk5wZzXrDIQC8ZeYNRAYyIA==} + engines: {node: '>=20.18.1'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.3.4: + resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@7.3.7: + resolution: {integrity: sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.12: + resolution: {integrity: sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.12 + '@vitest/browser-preview': 4.1.12 + '@vitest/browser-webdriverio': ^4.1.11 + '@vitest/coverage-istanbul': 4.1.12 + '@vitest/coverage-v8': 4.1.12 + '@vitest/ui': 4.1.12 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + + weak-lru-cache@1.2.3: + resolution: {integrity: sha512-pF0uTATIDYJFeEX4GDD5zjOe2jU9EJbBRcpcGPonUcao7eSp2hl6VA0b2Ims9jisMX33x6zgnozvaBCEsXNGPw==} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.2.0: + resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.6.6: + resolution: {integrity: sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==} + +snapshots: + + '@acemir/cssom@0.9.32': {} + + '@algolia/abtesting@1.14.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-abtesting@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-analytics@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-common@5.48.1': {} + + '@algolia/client-insights@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-personalization@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-query-suggestions@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-search@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/ingestion@1.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/monitoring@1.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/recommend@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/requester-browser-xhr@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + + '@algolia/requester-fetch@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + + '@algolia/requester-node-http@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@angular-devkit/architect@0.2102.29(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.29(chokidar@5.0.0) + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/core@21.2.29(chokidar@5.0.0)': + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1 + jsonc-parser: 3.3.1 + picomatch: 4.0.4 + rxjs: 7.8.2 + source-map: 0.7.6 + optionalDependencies: + chokidar: 5.0.0 + + '@angular-devkit/schematics@21.2.29(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.29(chokidar@5.0.0) + jsonc-parser: 3.3.1 + magic-string: 0.30.21 + ora: 9.3.0 + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + + '@angular/build@21.2.29(@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3))(@angular/compiler@21.2.26)(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(@angular/platform-browser@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)))(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(chokidar@5.0.0)(less@4.9.1)(ng-packagr@21.2.7(@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3))(tslib@2.9.1)(typescript@5.9.3))(postcss@8.5.32)(tslib@2.9.1)(typescript@5.9.3)(vitest@4.1.12(jsdom@28.1.0)(vite@7.3.7(less@4.9.1)(sass@1.97.3)))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@angular-devkit/architect': 0.2102.29(chokidar@5.0.0) + '@angular/compiler': 21.2.26 + '@angular/compiler-cli': 21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3) + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-split-export-declaration': 7.24.7 + '@inquirer/confirm': 5.1.21 + '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.7(less@4.9.1)(sass@1.97.3)) + beasties: 0.4.1 + browserslist: 4.29.5 + esbuild: 0.28.1 + https-proxy-agent: 7.0.6 + istanbul-lib-instrument: 6.0.3 + jsonc-parser: 3.3.1 + listr2: 9.0.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + parse5-html-rewriting-stream: 8.0.0 + picomatch: 4.0.4 + piscina: 5.3.2 + rolldown: 1.0.0-rc.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + sass: 1.97.3 + semver: 7.7.4 + source-map-support: 0.5.21 + tinyglobby: 0.2.15 + tslib: 2.9.1 + typescript: 5.9.3 + undici: 7.30.1 + vite: 7.3.7(less@4.9.1)(sass@1.97.3) + watchpack: 2.5.1 + optionalDependencies: + '@angular/core': 21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2) + '@angular/platform-browser': 21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)) + less: 4.9.1 + lmdb: 3.5.1 + ng-packagr: 21.2.7(@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3))(tslib@2.9.1)(typescript@5.9.3) + postcss: 8.5.32 + vitest: 4.1.12(jsdom@28.1.0)(vite@7.3.7(less@4.9.1)(sass@1.97.3)) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@types/node' + - chokidar + - jiti + - lightningcss + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@angular/cli@21.2.29(chokidar@5.0.0)': + dependencies: + '@angular-devkit/architect': 0.2102.29(chokidar@5.0.0) + '@angular-devkit/core': 21.2.29(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.29(chokidar@5.0.0) + '@inquirer/prompts': 7.10.1 + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1)(listr2@9.0.5) + '@modelcontextprotocol/sdk': 1.32.1(zod@4.6.6) + '@schematics/angular': 21.2.29(chokidar@5.0.0) + '@yarnpkg/lockfile': 1.1.0 + algoliasearch: 5.48.1 + ini: 6.0.0 + jsonc-parser: 3.3.1 + listr2: 9.0.5 + npm-package-arg: 13.0.2 + pacote: 21.5.1 + parse5-html-rewriting-stream: 8.0.0 + semver: 7.7.4 + yargs: 18.0.0 + zod: 4.6.6 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - chokidar + - supports-color + + '@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@angular/core': 21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2) + rxjs: 7.8.2 + tslib: 2.9.1 + + '@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3)': + dependencies: + '@angular/compiler': 21.2.26 + '@babel/core': 7.29.7 + '@jridgewell/sourcemap-codec': 1.6.0 + chokidar: 5.0.0 + convert-source-map: 1.9.0 + reflect-metadata: 0.2.2 + semver: 7.8.6 + tslib: 2.9.1 + yargs: 18.2.0 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@angular/compiler@21.2.26': + dependencies: + tslib: 2.9.1 + + '@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)': + dependencies: + rxjs: 7.8.2 + tslib: 2.9.1 + optionalDependencies: + '@angular/compiler': 21.2.26 + + '@angular/forms@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(@angular/platform-browser@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)))(rxjs@7.8.2)': + dependencies: + '@angular/common': 21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/core': 21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2) + '@angular/platform-browser': 21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)) + '@standard-schema/spec': 1.1.0 + rxjs: 7.8.2 + tslib: 2.9.1 + + '@angular/platform-browser@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))': + dependencies: + '@angular/common': 21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/core': 21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2) + tslib: 2.9.1 + + '@angular/router@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(@angular/platform-browser@21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)))(rxjs@7.8.2)': + dependencies: + '@angular/common': 21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/core': 21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2) + '@angular/platform-browser': 21.2.26(@angular/common@21.2.26(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.26(@angular/compiler@21.2.26)(rxjs@7.8.2)) + rxjs: 7.8.2 + tslib: 2.9.1 + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.7(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.1.0 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.13 + '@babel/helper-compilation-targets': 7.29.13 + '@babel/helper-module-transforms': 7.29.13(@babel/core@7.29.7) + '@babel/helpers': 7.29.13 + '@babel/parser': 7.29.13 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.13 + '@babel/types': 7.29.11 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.2 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.13': + dependencies: + '@babel/parser': 7.29.13 + '@babel/types': 7.29.11 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.11 + + '@babel/helper-compilation-targets@7.29.13': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.29.5 + lru-cache: 5.1.1 + semver: 6.3.2 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.13 + '@babel/types': 7.29.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.13(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.13 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.29.11 + + '@babel/helper-string-parser@7.29.13': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.13': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.11 + + '@babel/parser@7.29.13': + dependencies: + '@babel/types': 7.29.11 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.13 + '@babel/types': 7.29.11 + + '@babel/traverse@7.29.13': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.13 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.13 + '@babel/template': 7.29.7 + '@babel/types': 7.29.11 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.11': + dependencies: + '@babel/helper-string-parser': 7.29.13 + '@babel/helper-validator-identifier': 7.29.7 + + '@bramus/specificity@2.4.3': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.1.2': {} + + '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-color-parser@4.2.7(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/color-helpers': 6.1.2 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-syntax-patches-for-csstree@1.1.16(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.2': {} + + '@emnapi/core@1.11.3': + dependencies: + '@emnapi/wasi-threads': 1.2.3 + tslib: 2.9.1 + optional: true + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.9.1 + optional: true + + '@emnapi/wasi-threads@1.2.3': + dependencies: + tslib: 2.9.1 + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@exodus/bytes@1.16.0': {} + + '@gar/promise-retry@1.0.3': {} + + '@harperfast/extended-iterable@1.0.5': + optional: true + + '@hono/node-server@2.1.6(hono@4.13.17)': + dependencies: + hono: 4.13.17 + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 + + '@inquirer/confirm@5.1.21': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + + '@inquirer/core@10.3.2': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/editor@4.2.23': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/external-editor': 1.0.3 + '@inquirer/type': 3.0.10 + + '@inquirer/expand@4.0.23': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 + + '@inquirer/external-editor@1.0.3': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + + '@inquirer/number@3.0.23': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + + '@inquirer/password@4.0.23': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + + '@inquirer/prompts@7.10.1': + dependencies: + '@inquirer/checkbox': 4.3.2 + '@inquirer/confirm': 5.1.21 + '@inquirer/editor': 4.2.23 + '@inquirer/expand': 4.0.23 + '@inquirer/input': 4.3.1 + '@inquirer/number': 3.0.23 + '@inquirer/password': 4.0.23 + '@inquirer/rawlist': 4.1.11 + '@inquirer/search': 3.2.2 + '@inquirer/select': 4.4.2 + + '@inquirer/rawlist@4.1.11': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 + + '@inquirer/search@3.2.2': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 + + '@inquirer/select@4.4.2': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 + + '@inquirer/type@3.0.10': {} + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@istanbuljs/schema@0.1.6': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1)(listr2@9.0.5)': + dependencies: + '@inquirer/prompts': 7.10.1 + '@inquirer/type': 3.0.10 + listr2: 9.0.5 + transitivePeerDependencies: + - '@types/node' + + '@lmdb/lmdb-darwin-arm64@3.5.1': + optional: true + + '@lmdb/lmdb-darwin-x64@3.5.1': + optional: true + + '@lmdb/lmdb-linux-arm64@3.5.1': + optional: true + + '@lmdb/lmdb-linux-arm@3.5.1': + optional: true + + '@lmdb/lmdb-linux-x64@3.5.1': + optional: true + + '@lmdb/lmdb-win32-arm64@3.5.1': + optional: true + + '@lmdb/lmdb-win32-x64@3.5.1': + optional: true + + '@modelcontextprotocol/sdk@1.32.1(zod@4.6.6)': + dependencies: + '@hono/node-server': 2.1.6(hono@4.13.17) + ajv: 8.20.0 + ajv-formats: 3.0.1 + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.3.0 + express-rate-limit: 8.7.3(express@5.3.0) + hono: 4.13.17 + jose: 6.2.15 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.6.6 + zod-to-json-schema: 3.25.2(zod@4.6.6) + transitivePeerDependencies: + - supports-color + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.7': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.7': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.7': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.7': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.7': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.7': + optional: true + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@napi-rs/nice-android-arm-eabi@1.1.1': + optional: true + + '@napi-rs/nice-android-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-x64@1.1.1': + optional: true + + '@napi-rs/nice-freebsd-x64@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + optional: true + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-musl@1.1.1': + optional: true + + '@napi-rs/nice-openharmony-arm64@1.1.1': + optional: true + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + optional: true + + '@napi-rs/nice@1.1.1': + optionalDependencies: + '@napi-rs/nice-android-arm-eabi': 1.1.1 + '@napi-rs/nice-android-arm64': 1.1.1 + '@napi-rs/nice-darwin-arm64': 1.1.1 + '@napi-rs/nice-darwin-x64': 1.1.1 + '@napi-rs/nice-freebsd-x64': 1.1.1 + '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1 + '@napi-rs/nice-linux-arm64-gnu': 1.1.1 + '@napi-rs/nice-linux-arm64-musl': 1.1.1 + '@napi-rs/nice-linux-ppc64-gnu': 1.1.1 + '@napi-rs/nice-linux-riscv64-gnu': 1.1.1 + '@napi-rs/nice-linux-s390x-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-musl': 1.1.1 + '@napi-rs/nice-openharmony-arm64': 1.1.1 + '@napi-rs/nice-win32-arm64-msvc': 1.1.1 + '@napi-rs/nice-win32-ia32-msvc': 1.1.1 + '@napi-rs/nice-win32-x64-msvc': 1.1.1 + optional: true + + '@napi-rs/wasm-runtime@1.2.7(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + dependencies: + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.4 + optional: true + + '@npmcli/agent@4.0.2': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 11.5.3 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@5.0.0': + dependencies: + semver: 7.7.4 + + '@npmcli/git@7.0.2': + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/promise-spawn': 9.0.2 + ini: 6.0.0 + lru-cache: 11.5.3 + npm-pick-manifest: 11.0.3 + proc-log: 6.1.0 + semver: 7.7.4 + which: 6.0.1 + + '@npmcli/installed-package-contents@4.0.0': + dependencies: + npm-bundled: 5.0.0 + npm-normalize-package-bin: 5.0.0 + + '@npmcli/node-gyp@5.0.0': {} + + '@npmcli/package-json@7.0.5': + dependencies: + '@npmcli/git': 7.0.2 + glob: 13.0.7 + hosted-git-info: 9.0.3 + json-parse-even-better-errors: 5.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + spdx-expression-parse: 4.0.0 + + '@npmcli/promise-spawn@9.0.2': + dependencies: + which: 6.0.1 + + '@npmcli/redact@4.0.0': {} + + '@npmcli/run-script@10.0.4': + dependencies: + '@npmcli/node-gyp': 5.0.0 + '@npmcli/package-json': 7.0.5 + '@npmcli/promise-spawn': 9.0.2 + node-gyp: 12.4.0 + proc-log: 6.1.0 + + '@oxc-project/types@0.113.0': {} + + '@parcel/watcher-android-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-x64@2.6.0': + optional: true + + '@parcel/watcher-freebsd-x64@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.6.0': + optional: true + + '@parcel/watcher-win32-arm64@2.6.0': + optional: true + + '@parcel/watcher-win32-x64@2.6.0': + optional: true + + '@parcel/watcher@2.6.0': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.4 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.6.0 + '@parcel/watcher-darwin-arm64': 2.6.0 + '@parcel/watcher-darwin-x64': 2.6.0 + '@parcel/watcher-freebsd-x64': 2.6.0 + '@parcel/watcher-linux-arm-glibc': 2.6.0 + '@parcel/watcher-linux-arm-musl': 2.6.0 + '@parcel/watcher-linux-arm64-glibc': 2.6.0 + '@parcel/watcher-linux-arm64-musl': 2.6.0 + '@parcel/watcher-linux-x64-glibc': 2.6.0 + '@parcel/watcher-linux-x64-musl': 2.6.0 + '@parcel/watcher-win32-arm64': 2.6.0 + '@parcel/watcher-win32-x64': 2.6.0 + optional: true + + '@rolldown/binding-android-arm64@1.0.0-rc.4': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.4': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.4': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.4': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + dependencies: + '@napi-rs/wasm-runtime': 1.2.7(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.4': {} + + '@rollup/plugin-json@6.1.0(rollup@4.68.0)': + dependencies: + '@rollup/pluginutils': 5.5.1(rollup@4.68.0) + optionalDependencies: + rollup: 4.68.0 + + '@rollup/pluginutils@5.5.1(rollup@4.68.0)': + dependencies: + '@types/estree': 1.0.10 + estree-walker: 2.0.2 + picomatch: 4.0.7 + optionalDependencies: + rollup: 4.68.0 + + '@rollup/rollup-android-arm-eabi@4.68.0': + optional: true + + '@rollup/rollup-android-arm64@4.68.0': + optional: true + + '@rollup/rollup-android-x64@4.68.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.68.0': + optional: true + + '@rollup/rollup-darwin-x64@4.68.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.68.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.68.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.68.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.68.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.68.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.68.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.68.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.68.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.68.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.68.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.68.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.68.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.68.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.68.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.68.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.68.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.68.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.68.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.68.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.68.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.68.0': + optional: true + + '@rollup/wasm-node@4.68.0': + dependencies: + '@types/estree': 1.0.10 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + fsevents: 2.3.3 + + '@schematics/angular@21.2.29(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.29(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.29(chokidar@5.0.0) + jsonc-parser: 3.3.1 + transitivePeerDependencies: + - chokidar + + '@sigstore/bundle@4.0.0': + dependencies: + '@sigstore/protobuf-specs': 0.5.2 + + '@sigstore/core@3.2.1': {} + + '@sigstore/protobuf-specs@0.5.2': {} + + '@sigstore/sign@4.1.1': + dependencies: + '@gar/promise-retry': 1.0.3 + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.1 + '@sigstore/protobuf-specs': 0.5.2 + make-fetch-happen: 15.0.6 + proc-log: 6.1.0 + transitivePeerDependencies: + - supports-color + + '@sigstore/tuf@4.0.2': + dependencies: + '@sigstore/protobuf-specs': 0.5.2 + tuf-js: 4.1.0 + transitivePeerDependencies: + - supports-color + + '@sigstore/verify@3.1.1': + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.1 + '@sigstore/protobuf-specs': 0.5.2 + + '@standard-schema/spec@1.1.0': {} + + '@tufjs/canonical-json@2.0.0': {} + + '@tufjs/models@4.1.0': + dependencies: + '@tufjs/canonical-json': 2.0.0 + minimatch: 10.2.6 + + '@tybys/wasm-util@0.10.4': + dependencies: + tslib: 2.9.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.10': {} + + '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.7(less@4.9.1)(sass@1.97.3))': + dependencies: + vite: 7.3.7(less@4.9.1)(sass@1.97.3) + + '@vitest/expect@4.1.12': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.12 + '@vitest/utils': 4.1.12 + chai: 6.3.0 + tinyrainbow: 3.2.0 + + '@vitest/mocker@4.1.12(vite@7.3.7(less@4.9.1)(sass@1.97.3))': + dependencies: + '@vitest/spy': 4.1.12 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.7(less@4.9.1)(sass@1.97.3) + + '@vitest/pretty-format@4.1.12': + dependencies: + tinyrainbow: 3.2.0 + + '@vitest/runner@4.1.12': + dependencies: + '@vitest/utils': 4.1.12 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.12': + dependencies: + '@vitest/pretty-format': 4.1.12 + '@vitest/utils': 4.1.12 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.12': {} + + '@vitest/utils@4.1.12': + dependencies: + '@vitest/pretty-format': 4.1.12 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + + '@yarnpkg/lockfile@1.1.0': {} + + abbrev@4.0.0: {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + agent-base@7.1.4: {} + + ajv-formats@3.0.1: + dependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.9 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.9 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + algoliasearch@5.48.1: + dependencies: + '@algolia/abtesting': 1.14.1 + '@algolia/client-abtesting': 5.48.1 + '@algolia/client-analytics': 5.48.1 + '@algolia/client-common': 5.48.1 + '@algolia/client-insights': 5.48.1 + '@algolia/client-personalization': 5.48.1 + '@algolia/client-query-suggestions': 5.48.1 + '@algolia/client-search': 5.48.1 + '@algolia/ingestion': 1.48.1 + '@algolia/monitoring': 1.48.1 + '@algolia/recommend': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.5.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + assertion-error@2.0.1: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.11.35: {} + + beasties@0.4.1: + dependencies: + css-select: 6.0.0 + css-what: 7.0.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + htmlparser2: 10.1.0 + picocolors: 1.1.1 + postcss: 8.5.32 + postcss-media-query-parser: 0.2.3 + postcss-safe-parser: 7.1.0(postcss@8.5.32) + + bidi-js@1.1.0: + dependencies: + require-from-string: 2.0.2 + + body-parser@2.4.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.2 + qs: 6.17.1 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + boolbase@1.0.0: {} + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.29.5: + dependencies: + baseline-browser-mapping: 2.11.35 + caniuse-lite: 1.0.30001823 + electron-to-chromium: 1.5.478 + node-releases: 2.0.63 + update-browserslist-db: 1.3.4(browserslist@4.29.5) + + buffer-from@1.1.2: {} + + bytes@3.1.2: {} + + cacache@20.0.4: + dependencies: + '@npmcli/fs': 5.0.0 + fs-minipass: 3.0.3 + glob: 13.0.7 + lru-cache: 11.5.3 + minipass: 7.1.3 + minipass-collect: 2.0.1 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + p-map: 7.1.1 + ssri: 13.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + caniuse-lite@1.0.30001823: {} + + chai@6.3.0: {} + + chalk@5.6.2: {} + + chardet@2.2.0: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + chownr@3.0.0: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@3.4.0: {} + + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.3.0 + + cli-width@4.1.0: {} + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + commander@14.0.3: {} + + common-path-prefix@3.0.0: {} + + content-disposition@2.0.1: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@6.0.0: + dependencies: + boolbase: 1.0.0 + css-what: 7.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.2 + + css-what@7.0.0: {} + + cssstyle@6.2.0: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@csstools/css-syntax-patches-for-csstree': 1.1.16(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.5.3 + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + debug@3.2.7: + dependencies: + ms: 2.1.3 + optional: true + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + depd@2.0.0: {} + + dependency-graph@1.0.0: {} + + detect-libc@2.1.2: + optional: true + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.478: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.1: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + entities@7.0.1: {} + + entities@9.0.0: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.2: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.10 + + etag@1.8.1: {} + + eventemitter3@5.0.4: {} + + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + + expect-type@1.4.0: {} + + exponential-backoff@3.1.3: {} + + express-rate-limit@8.7.3(express@5.3.0): + dependencies: + debug: 4.4.3 + express: 5.3.0 + ip-address: 10.7.6 + transitivePeerDependencies: + - supports-color + + express@5.3.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.4.0 + content-disposition: 2.0.1 + content-type: 2.1.0 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.1 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.2 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.8 + qs: 6.17.1 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.9: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.1 + escape-html: 1.0.3 + on-finished: 2.4.2 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-cache-directory@6.0.0: + dependencies: + common-path-prefix: 3.0.0 + pkg-dir: 8.0.0 + + find-up-simple@1.0.1: {} + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.3 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.7.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + glob-to-regexp@0.4.1: {} + + glob@13.0.7: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hono@4.13.17: {} + + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.5.3 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.16.0 + transitivePeerDependencies: + - '@noble/hashes' + + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + + http-cache-semantics@4.3.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ignore-walk@8.0.0: + dependencies: + minimatch: 10.2.6 + + immutable@5.1.9: {} + + inherits@2.0.4: {} + + ini@6.0.0: {} + + injection-js@2.6.1: + dependencies: + tslib: 2.9.1 + + ip-address@10.7.6: {} + + ipaddr.js@1.9.1: {} + + is-extglob@2.1.1: + optional: true + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.7.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + optional: true + + is-interactive@2.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-promise@4.0.0: {} + + is-unicode-supported@2.1.0: {} + + is-what@4.1.16: {} + + isexe@2.0.0: {} + + isexe@4.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.13 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + jose@6.2.15: {} + + js-tokens@4.0.0: {} + + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.32 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.3 + '@exodus/bytes': 1.16.0 + cssstyle: 6.2.0 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.1.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.1.0 + undici: 7.30.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + + jsesc@3.1.0: {} + + json-parse-even-better-errors@5.0.0: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsonparse@1.3.1: {} + + less@4.9.1: + dependencies: + copy-anything: 3.0.5 + parse-node-version: 1.0.1 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + make-dir: 5.1.0 + mime: 1.6.0 + needle: 3.5.0 + probe-image-size: 7.5.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + listr2@9.0.5: + dependencies: + cli-truncate: 5.2.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + lmdb@3.5.1: + dependencies: + '@harperfast/extended-iterable': 1.0.5 + msgpackr: 1.12.1 + node-addon-api: 6.1.0 + node-gyp-build-optional-packages: 5.2.2 + ordered-binary: 1.6.3 + weak-lru-cache: 1.2.3 + optionalDependencies: + '@lmdb/lmdb-darwin-arm64': 3.5.1 + '@lmdb/lmdb-darwin-x64': 3.5.1 + '@lmdb/lmdb-linux-arm': 3.5.1 + '@lmdb/lmdb-linux-arm64': 3.5.1 + '@lmdb/lmdb-linux-x64': 3.5.1 + '@lmdb/lmdb-win32-arm64': 3.5.1 + '@lmdb/lmdb-win32-x64': 3.5.1 + optional: true + + lodash.merge@4.6.2: + optional: true + + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.2.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + lru-cache@11.5.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + make-dir@5.1.0: + optional: true + + make-fetch-happen@15.0.6: + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/agent': 4.0.2 + '@npmcli/redact': 4.0.0 + cacache: 20.0.4 + http-cache-semantics: 4.3.0 + minipass: 7.1.3 + minipass-fetch: 5.0.2 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + negotiator: 1.1.0 + proc-log: 6.1.0 + ssri: 13.0.1 + transitivePeerDependencies: + - supports-color + + math-intrinsics@1.1.0: {} + + mdn-data@2.27.1: {} + + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: + optional: true + + mimic-function@5.0.1: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.3 + + minipass-fetch@5.0.2: + dependencies: + minipass: 7.1.3 + minipass-sized: 2.0.0 + minizlib: 3.1.0 + optionalDependencies: + iconv-lite: 0.7.3 + + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@2.0.0: + dependencies: + minipass: 7.1.3 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + msgpackr-extract@3.0.7: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.7 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.7 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.7 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.7 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.7 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.7 + optional: true + + msgpackr@1.12.1: + optionalDependencies: + msgpackr-extract: 3.0.7 + optional: true + + mute-stream@2.0.0: {} + + nanoid@3.3.20: {} + + needle@2.9.1: + dependencies: + debug: 3.2.7 + iconv-lite: 0.4.24 + sax: 1.6.1 + transitivePeerDependencies: + - supports-color + optional: true + + needle@3.5.0: + dependencies: + iconv-lite: 0.6.3 + sax: 1.6.1 + optional: true + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + ng-packagr@21.2.7(@angular/compiler-cli@21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3))(tslib@2.9.1)(typescript@5.9.3): + dependencies: + '@ampproject/remapping': 2.3.0 + '@angular/compiler-cli': 21.2.26(@angular/compiler@21.2.26)(typescript@5.9.3) + '@rollup/plugin-json': 6.1.0(rollup@4.68.0) + '@rollup/wasm-node': 4.68.0 + ajv: 8.20.0 + ansi-colors: 4.1.3 + browserslist: 4.29.5 + chokidar: 5.0.0 + commander: 14.0.3 + dependency-graph: 1.0.0 + esbuild: 0.28.2 + find-cache-directory: 6.0.0 + injection-js: 2.6.1 + jsonc-parser: 3.3.1 + less: 4.9.1 + ora: 9.4.1 + piscina: 5.3.2 + postcss: 8.5.32 + rollup-plugin-dts: 6.4.1(rollup@4.68.0)(typescript@5.9.3) + rxjs: 7.8.2 + sass: 1.108.1 + tinyglobby: 0.2.17 + tslib: 2.9.1 + typescript: 5.9.3 + optionalDependencies: + rollup: 4.68.0 + transitivePeerDependencies: + - supports-color + + node-addon-api@6.1.0: + optional: true + + node-addon-api@7.1.1: + optional: true + + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 + optional: true + + node-gyp@12.4.0: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + graceful-fs: 4.2.11 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + tar: 7.5.22 + tinyglobby: 0.2.17 + undici: 6.29.1 + which: 6.0.1 + + node-releases@2.0.63: {} + + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + + npm-bundled@5.0.0: + dependencies: + npm-normalize-package-bin: 5.0.0 + + npm-install-checks@8.0.0: + dependencies: + semver: 7.7.4 + + npm-normalize-package-bin@5.0.0: {} + + npm-package-arg@13.0.2: + dependencies: + hosted-git-info: 9.0.3 + proc-log: 6.1.0 + semver: 7.7.4 + validate-npm-package-name: 7.0.2 + + npm-packlist@10.0.4: + dependencies: + ignore-walk: 8.0.0 + proc-log: 6.1.0 + + npm-pick-manifest@11.0.3: + dependencies: + npm-install-checks: 8.0.0 + npm-normalize-package-bin: 5.0.0 + npm-package-arg: 13.0.2 + semver: 7.7.4 + + npm-registry-fetch@19.1.1: + dependencies: + '@npmcli/redact': 4.0.0 + jsonparse: 1.3.1 + make-fetch-happen: 15.0.6 + minipass: 7.1.3 + minipass-fetch: 5.0.2 + minizlib: 3.1.0 + npm-package-arg: 13.0.2 + proc-log: 6.1.0 + transitivePeerDependencies: + - supports-color + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + obug@2.2.1: {} + + on-finished@2.4.2: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + ora@9.3.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.3.0 + + ora@9.4.1: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.3.0 + + ordered-binary@1.6.3: + optional: true + + p-map@7.1.1: {} + + pacote@21.5.1: + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/git': 7.0.2 + '@npmcli/installed-package-contents': 4.0.0 + '@npmcli/package-json': 7.0.5 + '@npmcli/promise-spawn': 9.0.2 + '@npmcli/run-script': 10.0.4 + cacache: 20.0.4 + fs-minipass: 3.0.3 + minipass: 7.1.3 + npm-package-arg: 13.0.2 + npm-packlist: 10.0.4 + npm-pick-manifest: 11.0.3 + npm-registry-fetch: 19.1.1 + proc-log: 6.1.0 + sigstore: 4.1.1 + ssri: 13.0.1 + tar: 7.5.22 + transitivePeerDependencies: + - supports-color + + parse-node-version@1.0.1: {} + + parse5-html-rewriting-stream@8.0.0: + dependencies: + entities: 6.0.1 + parse5: 8.1.0 + parse5-sax-parser: 8.0.0 + + parse5-sax-parser@8.0.0: + dependencies: + parse5: 8.1.0 + + parse5@8.1.0: + dependencies: + entities: 9.0.0 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.3 + minipass: 7.1.3 + + path-to-regexp@8.4.2: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + picomatch@4.0.7: {} + + piscina@5.3.2: + optionalDependencies: + '@napi-rs/nice': 1.1.1 + + pkce-challenge@5.0.1: {} + + pkg-dir@8.0.0: + dependencies: + find-up-simple: 1.0.1 + + postcss-media-query-parser@0.2.3: {} + + postcss-safe-parser@7.1.0(postcss@8.5.32): + dependencies: + postcss: 8.5.32 + + postcss@8.5.32: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + prettier@3.9.10: {} + + probe-image-size@7.5.0: + dependencies: + lodash.merge: 4.6.2 + needle: 2.9.1 + transitivePeerDependencies: + - supports-color + optional: true + + proc-log@6.1.0: {} + + proxy-addr@2.0.8: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + prr@1.0.1: + optional: true + + punycode@2.3.1: {} + + qs@6.17.1: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + readdirp@4.1.2: {} + + readdirp@5.1.1: {} + + reflect-metadata@0.2.2: {} + + require-from-string@2.0.2: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rfdc@1.4.1: {} + + rolldown@1.0.0-rc.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): + dependencies: + '@oxc-project/types': 0.113.0 + '@rolldown/pluginutils': 1.0.0-rc.4 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.4 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.4 + '@rolldown/binding-darwin-x64': 1.0.0-rc.4 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.4 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.4 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.4 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.4 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.4 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.4 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.4 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.4 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + rollup-plugin-dts@6.4.1(rollup@4.68.0)(typescript@5.9.3): + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.6.0 + convert-source-map: 2.0.0 + magic-string: 0.30.21 + rollup: 4.68.0 + typescript: 5.9.3 + optionalDependencies: + '@babel/code-frame': 7.29.7 + + rollup@4.68.0: + dependencies: + '@types/estree': 1.0.10 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.68.0 + '@rollup/rollup-android-arm64': 4.68.0 + '@rollup/rollup-android-x64': 4.68.0 + '@rollup/rollup-darwin-arm64': 4.68.0 + '@rollup/rollup-darwin-x64': 4.68.0 + '@rollup/rollup-freebsd-arm64': 4.68.0 + '@rollup/rollup-freebsd-x64': 4.68.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.68.0 + '@rollup/rollup-linux-arm-musleabihf': 4.68.0 + '@rollup/rollup-linux-arm64-gnu': 4.68.0 + '@rollup/rollup-linux-arm64-musl': 4.68.0 + '@rollup/rollup-linux-loong64-gnu': 4.68.0 + '@rollup/rollup-linux-loong64-musl': 4.68.0 + '@rollup/rollup-linux-ppc64-gnu': 4.68.0 + '@rollup/rollup-linux-ppc64-musl': 4.68.0 + '@rollup/rollup-linux-riscv64-gnu': 4.68.0 + '@rollup/rollup-linux-riscv64-musl': 4.68.0 + '@rollup/rollup-linux-s390x-gnu': 4.68.0 + '@rollup/rollup-linux-x64-gnu': 4.68.0 + '@rollup/rollup-linux-x64-musl': 4.68.0 + '@rollup/rollup-openbsd-x64': 4.68.0 + '@rollup/rollup-openharmony-arm64': 4.68.0 + '@rollup/rollup-win32-arm64-msvc': 4.68.0 + '@rollup/rollup-win32-ia32-msvc': 4.68.0 + '@rollup/rollup-win32-x64-gnu': 4.68.0 + '@rollup/rollup-win32-x64-msvc': 4.68.0 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + rxjs@7.8.2: + dependencies: + tslib: 2.9.1 + + safer-buffer@2.1.2: {} + + sass@1.108.1: + dependencies: + chokidar: 5.0.0 + immutable: 5.1.9 + source-map-js: 1.2.2 + optionalDependencies: + '@parcel/watcher': 2.6.0 + + sass@1.97.3: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.9 + source-map-js: 1.2.2 + optionalDependencies: + '@parcel/watcher': 2.6.0 + + sax@1.6.1: + optional: true + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@6.3.2: {} + + semver@7.7.4: {} + + semver@7.8.6: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.1 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.2 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.1 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + sigstore@4.1.1: + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.1 + '@sigstore/protobuf-specs': 0.5.2 + '@sigstore/sign': 4.1.1 + '@sigstore/tuf': 4.0.2 + '@sigstore/verify': 3.1.1 + transitivePeerDependencies: + - supports-color + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.10 + transitivePeerDependencies: + - supports-color + + socks@2.8.10: + dependencies: + ip-address: 10.7.6 + smart-buffer: 4.2.0 + + source-map-js@1.2.2: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.24 + + spdx-license-ids@3.0.24: {} + + ssri@13.0.1: + dependencies: + minipass: 7.1.3 + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@4.3.0: {} + + stdin-discarder@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string-width@8.3.0: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.5.0 + + symbol-tree@3.2.4: {} + + tar@7.5.22: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + tinybench@2.9.0: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.2.0: {} + + tldts-core@7.4.20: {} + + tldts@7.4.19: + dependencies: + tldts-core: 7.4.20 + + toidentifier@1.0.1: {} + + tough-cookie@6.1.0: + dependencies: + tldts: 7.4.19 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + tslib@2.9.1: {} + + tuf-js@4.1.0: + dependencies: + '@tufjs/models': 4.1.0 + debug: 4.4.3 + make-fetch-happen: 15.0.6 + transitivePeerDependencies: + - supports-color + + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici@6.29.1: {} + + undici@7.30.1: {} + + undici@7.30.2: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.3.4(browserslist@4.29.5): + dependencies: + browserslist: 4.29.5 + escalade: 3.2.0 + picocolors: 1.1.1 + + validate-npm-package-name@7.0.2: {} + + vary@1.1.2: {} + + vite@7.3.7(less@4.9.1)(sass@1.97.3): + dependencies: + esbuild: 0.28.1 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.32 + rollup: 4.68.0 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + less: 4.9.1 + sass: 1.97.3 + + vitest@4.1.12(jsdom@28.1.0)(vite@7.3.7(less@4.9.1)(sass@1.97.3)): + dependencies: + '@vitest/expect': 4.1.12 + '@vitest/mocker': 4.1.12(vite@7.3.7(less@4.9.1)(sass@1.97.3)) + '@vitest/pretty-format': 4.1.12 + '@vitest/runner': 4.1.12 + '@vitest/snapshot': 4.1.12 + '@vitest/spy': 4.1.12 + '@vitest/utils': 4.1.12 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.2.1 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 2.9.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tinyrainbow: 3.2.0 + vite: 7.3.7(less@4.9.1)(sass@1.97.3) + why-is-node-running: 2.3.0 + optionalDependencies: + jsdom: 28.1.0 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + watchpack@2.5.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + weak-lru-cache@1.2.3: + optional: true + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.16.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@6.0.1: + dependencies: + isexe: 4.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yallist@5.0.0: {} + + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yargs@18.2.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 8.3.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yoctocolors-cjs@2.1.3: {} + + yoctocolors@2.2.0: {} + + zod-to-json-schema@3.25.2(zod@4.6.6): + dependencies: + zod: 4.6.6 + + zod@4.6.6: {} diff --git a/examples/enterprise/acme-ui/pnpm-workspace.yaml b/examples/enterprise/acme-ui/pnpm-workspace.yaml new file mode 100644 index 0000000..1590fdd --- /dev/null +++ b/examples/enterprise/acme-ui/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +# The team decided which dependencies may run install scripts: none of these need theirs to build or serve. +allowBuilds: + '@parcel/watcher': false + esbuild: false + lmdb: false + msgpackr-extract: false diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/README.md b/examples/enterprise/acme-ui/projects/acme/ui-core/README.md new file mode 100644 index 0000000..5d1d471 --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/README.md @@ -0,0 +1,64 @@ +# UiCore + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the library, run: + +```bash +ng build ui-core +``` + +This command will compile your project, and the build artifacts will be placed in the `dist/` directory. + +### Publishing the Library + +Once the project is built, you can publish your library by following these steps: + +1. Navigate to the `dist` directory: + + ```bash + cd dist/ui-core + ``` + +2. Run the `npm publish` command to publish your library to the npm registry: + ```bash + npm publish + ``` + +## Running unit tests + +To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/ng-package.json b/examples/enterprise/acme-ui/projects/acme/ui-core/ng-package.json new file mode 100644 index 0000000..8ec7cca --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/ng-package.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../dist/acme/ui-core", + "lib": { + "entryFile": "src/public-api.ts" + }, + "assets": [ + "theme.css" + ] +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/package.json b/examples/enterprise/acme-ui/projects/acme/ui-core/package.json new file mode 100644 index 0000000..98768ba --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/package.json @@ -0,0 +1,13 @@ +{ + "name": "@acme/ui-core", + "version": "21.4.0", + "peerDependencies": { + "@angular/common": "^21.2.0", + "@angular/core": "^21.2.0" + }, + "dependencies": { + "tslib": "^2.3.0" + }, + "sideEffects": false, + "description": "Acme's base components and design tokens." +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/button.ts b/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/button.ts new file mode 100644 index 0000000..79b3f9b --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/button.ts @@ -0,0 +1,12 @@ +import { Component, input } from '@angular/core'; + +/** The house button: `primary` for the one action that matters on a screen, `quiet` for the rest. */ +@Component({ + selector: 'acme-button', + template: ``, +}) +export class AcmeButton { + readonly variant = input<'primary' | 'quiet'>('primary'); + readonly type = input<'button' | 'submit'>('button'); + readonly disabled = input(false); +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/card.ts b/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/card.ts new file mode 100644 index 0000000..cf6419c --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/card.ts @@ -0,0 +1,10 @@ +import { Component, input } from '@angular/core'; + +/** A titled panel that holds one task on a screen. */ +@Component({ + selector: 'acme-card', + template: `

{{ heading() }}

`, +}) +export class AcmeCard { + readonly heading = input.required(); +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/ui-core.spec.ts b/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/ui-core.spec.ts new file mode 100644 index 0000000..0f5782a --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/src/lib/ui-core.spec.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { AcmeButton } from './button'; +import { AcmeCard } from './card'; + +@Component({ + imports: [AcmeButton, AcmeCard], + template: `Send`, +}) +class Host {} + +describe('ui-core', () => { + it('renders a card with its heading, and a button with its variant and type', async () => { + const fixture = TestBed.createComponent(Host); + await fixture.whenStable(); + const element = fixture.nativeElement as HTMLElement; + expect(element.querySelector('.acme-card__title')?.textContent).toBe('Requests'); + const button = element.querySelector('button'); + expect(button?.className).toBe('acme-button acme-button--quiet'); + expect(button?.type).toBe('submit'); + expect(button?.textContent).toBe('Send'); + }); +}); diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/src/public-api.ts b/examples/enterprise/acme-ui/projects/acme/ui-core/src/public-api.ts new file mode 100644 index 0000000..bd71fb2 --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/src/public-api.ts @@ -0,0 +1,6 @@ +/* + * Public API Surface of @acme/ui-core + */ + +export * from './lib/button'; +export * from './lib/card'; diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/theme.css b/examples/enterprise/acme-ui/projects/acme/ui-core/theme.css new file mode 100644 index 0000000..b84948e --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/theme.css @@ -0,0 +1,66 @@ +/* Acme's design tokens and the base styles for its components. Apps load this file from angular.json's styles. */ +:root { + --acme-ink: #1d2433; + --acme-muted: #5b6473; + --acme-surface: #ffffff; + --acme-canvas: #f3f5f9; + --acme-brand: #2f5bea; + --acme-brand-ink: #ffffff; + --acme-radius: 10px; + font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; + color: var(--acme-ink); + background: var(--acme-canvas); +} + +.acme-button { + border: 1px solid var(--acme-brand); + border-radius: var(--acme-radius); + padding: 0.5rem 1rem; + font: inherit; + cursor: pointer; +} + +.acme-button--primary { + background: var(--acme-brand); + color: var(--acme-brand-ink); +} + +.acme-button--quiet { + background: transparent; + color: var(--acme-brand); +} + +.acme-button:disabled { + opacity: 0.5; + cursor: default; +} + +.acme-card { + background: var(--acme-surface); + border-radius: var(--acme-radius); + padding: 1.25rem; + box-shadow: 0 1px 3px rgb(0 0 0 / 0.08); +} + +.acme-card__title { + margin: 0 0 1rem; + font-size: 1.125rem; +} + +.acme-field { + display: grid; + gap: 0.25rem; + margin-bottom: 0.75rem; +} + +.acme-field__label { + color: var(--acme-muted); + font-size: 0.875rem; +} + +.acme-field__input { + border: 1px solid #c9cfda; + border-radius: var(--acme-radius); + padding: 0.5rem 0.75rem; + font: inherit; +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.lib.json b/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.lib.json new file mode 100644 index 0000000..cab461f --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.lib.json @@ -0,0 +1,17 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/lib", + "declaration": true, + "declarationMap": true, + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts" + ] +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.lib.prod.json b/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.lib.prod.json new file mode 100644 index 0000000..9215caa --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.lib.prod.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.spec.json b/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.spec.json new file mode 100644 index 0000000..98e89a6 --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-core/tsconfig.spec.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/spec", + "types": [ + "vitest/globals" + ] + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.spec.ts" + ] +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/README.md b/examples/enterprise/acme-ui/projects/acme/ui-forms/README.md new file mode 100644 index 0000000..932879d --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/README.md @@ -0,0 +1,64 @@ +# UiForms + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the library, run: + +```bash +ng build ui-forms +``` + +This command will compile your project, and the build artifacts will be placed in the `dist/` directory. + +### Publishing the Library + +Once the project is built, you can publish your library by following these steps: + +1. Navigate to the `dist` directory: + + ```bash + cd dist/ui-forms + ``` + +2. Run the `npm publish` command to publish your library to the npm registry: + ```bash + npm publish + ``` + +## Running unit tests + +To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/ng-package.json b/examples/enterprise/acme-ui/projects/acme/ui-forms/ng-package.json new file mode 100644 index 0000000..0f9664a --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../dist/acme/ui-forms", + "lib": { + "entryFile": "src/public-api.ts" + } +} \ No newline at end of file diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/package.json b/examples/enterprise/acme-ui/projects/acme/ui-forms/package.json new file mode 100644 index 0000000..a3465a4 --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/package.json @@ -0,0 +1,15 @@ +{ + "name": "@acme/ui-forms", + "version": "21.4.0", + "peerDependencies": { + "@angular/common": "^21.2.0", + "@angular/core": "^21.2.0", + "@angular/forms": "^21.2.0", + "@acme/ui-core": "^21.4.0" + }, + "dependencies": { + "tslib": "^2.3.0" + }, + "sideEffects": false, + "description": "Acme's form fields, built on @acme/ui-core." +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/src/lib/text-field.spec.ts b/examples/enterprise/acme-ui/projects/acme/ui-forms/src/lib/text-field.spec.ts new file mode 100644 index 0000000..1597df6 --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/src/lib/text-field.spec.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { AcmeTextField } from './text-field'; + +@Component({ + imports: [AcmeTextField, ReactiveFormsModule], + template: ``, +}) +class Host { + readonly title = new FormControl('Laptop'); +} + +describe('AcmeTextField', () => { + it('shows the form value, writes typing back, and clears it', async () => { + const fixture = TestBed.createComponent(Host); + await fixture.whenStable(); + const element = fixture.nativeElement as HTMLElement; + const input = element.querySelector('input')!; + expect(element.querySelector('.acme-field__label')?.textContent).toBe('Title'); + expect(input.value).toBe('Laptop'); + + input.value = 'Monitor'; + input.dispatchEvent(new Event('input')); + expect(fixture.componentInstance.title.value).toBe('Monitor'); + + element.querySelector('button')!.click(); + await fixture.whenStable(); + expect(fixture.componentInstance.title.value).toBe(''); + expect(element.querySelector('button')).toBeNull(); + }); +}); diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/src/lib/text-field.ts b/examples/enterprise/acme-ui/projects/acme/ui-forms/src/lib/text-field.ts new file mode 100644 index 0000000..84862e6 --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/src/lib/text-field.ts @@ -0,0 +1,36 @@ +import { Component, forwardRef, input, signal } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { AcmeButton } from '@acme/ui-core'; + +/** A labelled text input for Angular forms, with a button to clear it. */ +@Component({ + selector: 'acme-text-field', + imports: [AcmeButton], + template: ` + + @if (value() && !disabled()) { + Clear + } + `, + providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AcmeTextField), multi: true }], +}) +export class AcmeTextField implements ControlValueAccessor { + readonly label = input.required(); + protected readonly value = signal(''); + protected readonly disabled = signal(false); + private changed: (value: string) => void = () => {}; + protected touched: () => void = () => {}; + + writeValue(value: string | null): void { this.value.set(value ?? ''); } + registerOnChange(fn: (value: string) => void): void { this.changed = fn; } + registerOnTouched(fn: () => void): void { this.touched = fn; } + setDisabledState(disabled: boolean): void { this.disabled.set(disabled); } + + protected update(value: string): void { + this.value.set(value); + this.changed(value); + } +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/src/public-api.ts b/examples/enterprise/acme-ui/projects/acme/ui-forms/src/public-api.ts new file mode 100644 index 0000000..13cb42b --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/src/public-api.ts @@ -0,0 +1,5 @@ +/* + * Public API Surface of @acme/ui-forms + */ + +export * from './lib/text-field'; diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.lib.json b/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.lib.json new file mode 100644 index 0000000..cab461f --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.lib.json @@ -0,0 +1,17 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/lib", + "declaration": true, + "declarationMap": true, + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts" + ] +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.lib.prod.json b/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.lib.prod.json new file mode 100644 index 0000000..9215caa --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.lib.prod.json @@ -0,0 +1,11 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.spec.json b/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.spec.json new file mode 100644 index 0000000..98e89a6 --- /dev/null +++ b/examples/enterprise/acme-ui/projects/acme/ui-forms/tsconfig.spec.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/spec", + "types": [ + "vitest/globals" + ] + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.spec.ts" + ] +} diff --git a/examples/enterprise/acme-ui/tsconfig.json b/examples/enterprise/acme-ui/tsconfig.json new file mode 100644 index 0000000..5f89d5a --- /dev/null +++ b/examples/enterprise/acme-ui/tsconfig.json @@ -0,0 +1,47 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "paths": { + "@acme/ui-core": [ + "./dist/acme/ui-core" + ], + "@acme/ui-forms": [ + "./dist/acme/ui-forms" + ] + }, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./projects/acme/ui-core/tsconfig.lib.json" + }, + { + "path": "./projects/acme/ui-core/tsconfig.spec.json" + }, + { + "path": "./projects/acme/ui-forms/tsconfig.lib.json" + }, + { + "path": "./projects/acme/ui-forms/tsconfig.spec.json" + } + ] +} diff --git a/examples/enterprise/registry/config.yaml b/examples/enterprise/registry/config.yaml new file mode 100644 index 0000000..ac28abb --- /dev/null +++ b/examples/enterprise/registry/config.yaml @@ -0,0 +1,12 @@ +# A stand-in for a company registry: Acme's packages are published here, and everything else still comes from npm, +# because each repo's .npmrc only sends the @acme scope here. Anyone on this machine may publish, which a real registry +# wouldn't allow, so the example stays on the upgrade rather than on logging in. Run it with: +# npx verdaccio@7.0.2 --config examples/enterprise/registry/config.yaml +storage: ./storage +packages: + '@acme/*': + access: $all + publish: $all +listen: 127.0.0.1:4873 +# At info it prints its address and "server started" once it's ready, then a line for each request. +log: { type: stdout, format: pretty, level: info } diff --git a/examples/enterprise/setup.mjs b/examples/enterprise/setup.mjs new file mode 100644 index 0000000..73a2645 --- /dev/null +++ b/examples/enterprise/setup.mjs @@ -0,0 +1,80 @@ +#!/usr/bin/env node +// Sets up the enterprise example: two repositories, as a company would have them. acme-ui holds Acme's UI libraries, +// published to a local registry that stands in for the company one, and acme-portal is the app that uses them. +// Both are on Angular 21, and the libraries' peer ranges stop there, so the portal can't move to 22 until they do. +// Start the registry first, in another terminal: +// npx verdaccio@7.0.2 --config examples/enterprise/registry/config.yaml +// Usage: node examples/enterprise/setup.mjs +import { spawnSync } from 'node:child_process'; +import { cpSync, existsSync, readFileSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = fileURLToPath(new URL('.', import.meta.url)); +const REGISTRY = 'http://localhost:4873/'; +const START_REGISTRY = 'npx verdaccio@7.0.2 --config examples/enterprise/registry/config.yaml'; +const target = process.argv[2] && resolve(process.argv[2]); + +function run(cmd, args, cwd) { + console.log(` ${cmd} ${args.join(' ')}`); + // pnpm on Windows is a .cmd shim, which only runs through a shell. Its arguments here are fixed, none from input and + // none with a space, so they go as one command line: Node would join them the same way, but warns when it has to. git + // runs without a shell, since a shell would split an argument with a space in it. + const shell = process.platform === 'win32' && cmd === 'pnpm'; + const result = spawnSync(shell ? `${cmd} ${args.join(' ')}` : cmd, shell ? [] : args, { cwd, stdio: 'inherit', shell, env: { ...process.env, NG_CLI_ANALYTICS: 'false' } }); + if (result.status !== 0) throw new Error(`${cmd} ${args.join(' ')} failed in ${cwd}`); +} + +async function versions(name) { + const res = await fetch(`${REGISTRY}${name.replace('/', '%2f')}`); + return res.ok ? Object.keys((await res.json()).versions ?? {}) : []; +} + +async function main() { + if (!target) throw new Error('Usage: node examples/enterprise/setup.mjs '); + const ping = await fetch(`${REGISTRY}-/ping`).catch(() => { throw new Error(`No registry answers at ${REGISTRY}. Start it first:\n ${START_REGISTRY}`); }); + if (!ping.ok) throw new Error(`The server at ${REGISTRY} answered ${ping.status} to a ping. Stop whatever holds port 4873, then start the example's registry:\n ${START_REGISTRY}`); + // 4873 is Verdaccio's default port, so another registry may answer there, and setup would publish Acme's libraries to + // it. The example's has only @acme packages, so one that serves a public package such as react isn't it. Nor is one + // that wants a login to read @acme packages, since the example's lets anyone read them. + if ((await fetch(`${REGISTRY}react`)).ok) throw new Error(`The registry at ${REGISTRY} serves react, so it isn't the example's, which has only @acme packages. Stop it or move it to another port, then start the example's:\n ${START_REGISTRY}`); + if ([401, 403].includes((await fetch(`${REGISTRY}@acme%2fui-core`)).status)) throw new Error(`The registry at ${REGISTRY} wants a login to read @acme packages, so it isn't the example's, which lets anyone read them. Stop it or move it to another port, then start the example's:\n ${START_REGISTRY}`); + // The registry keeps what earlier runs published, and a later release there would leave the portal nothing to wait for. + for (const name of ['@acme/ui-core', '@acme/ui-forms']) { + const later = (await versions(name)).find((v) => !v.startsWith('21.')); + if (later) throw new Error(`The registry already has ${name}@${later} from an earlier run, so the portal wouldn't be blocked. To start over, stop the registry, delete examples/enterprise/registry/storage and start it again.`); + } + for (const repo of ['acme-ui', 'acme-portal']) { + if (existsSync(join(target, repo))) throw new Error(`${join(target, repo)} already exists. Pick an empty folder.`); + } + + for (const repo of ['acme-ui', 'acme-portal']) { + console.log(`\n${repo}: copying to ${join(target, repo)}`); + cpSync(join(HERE, repo), join(target, repo), { recursive: true, filter: (src) => !/[\\/](node_modules|dist|\.angular)$/.test(src) }); + } + + const ui = join(target, 'acme-ui'); + console.log('\nacme-ui: install, build, test and publish the 21.x libraries'); + run('pnpm', ['install', '--frozen-lockfile'], ui); + run('pnpm', ['run', 'build'], ui); + run('pnpm', ['test'], ui); + for (const lib of ['ui-core', 'ui-forms']) { + const { name, version } = JSON.parse(readFileSync(join(ui, 'dist', 'acme', lib, 'package.json'), 'utf8')); + if ((await versions(name)).includes(version)) console.log(` ${name}@${version} is already in the registry, so it's left as it is`); + else run('pnpm', ['publish', join('dist', 'acme', lib), '--no-git-checks'], ui); + } + + const portal = join(target, 'acme-portal'); + console.log('\nacme-portal: install against the published libraries, and build'); + run('pnpm', ['install'], portal); + run('pnpm', ['run', 'build'], portal); + + for (const dir of [ui, portal]) { + run('git', ['init', '-q', '-b', 'main'], dir); + run('git', ['add', '-A'], dir); + run('git', ['-c', 'user.name=Acme Team', '-c', 'user.email=team@acme.example', 'commit', '-q', '-m', 'chore: start on Angular 21'], dir); + } + console.log(`\nReady. Open ${portal} in your agent, with ng-hop installed, and ask:\n Upgrade this app to Angular 22.`); +} + +main().catch((err) => { console.error(err.message); process.exitCode = 1; }); diff --git a/plugins/ng-hop/skills/ng-hop/SKILL.md b/plugins/ng-hop/skills/ng-hop/SKILL.md index 86a8bc9..8839bfb 100644 --- a/plugins/ng-hop/skills/ng-hop/SKILL.md +++ b/plugins/ng-hop/skills/ng-hop/SKILL.md @@ -37,11 +37,12 @@ It reads `package.json`, the lockfile and npm, and changes nothing. Exit 0 means Show the person the result in a few lines: where they can get to, what blocks anything further, and which libraries are unconfirmed. - **If they only asked a question** ("how far can I go?", "does PrimeNG support 22?", "is this upgrade safe?"), answer from the plan and stop. Change nothing. -- **If it's already on the target major with patch releases available,** and they asked to upgrade, create the branch as the paragraph below says, install dependencies as in step 3, run the command it prints, check it as in step 4f, and commit. That's the whole job. +- **If it's already on the target major,** and they asked to upgrade: with patch releases available, create the branch as the paragraph below says, install dependencies as in step 3, run the command it prints, check it as in step 4f, and commit. If the plan also lists Node pins below what this major needs, raise them as step 4e says, in that commit, or in a commit of their own on the branch when there's nothing else to upgrade, because an earlier upgrade left them failing on the team's machines. That's the whole job. - **If the plan is blocked,** carry on up to the highest reachable major, then stop as step 5 describes. Say that's what you're doing. - **A note that a library's license changes on a major,** or that a library brings packages under a license that changed or isn't a common open-source one: the build, tests and app check all pass after a change like this, so the plan is the only warning they'll get, and it can cost them money. Read both license files (`npm pack @` in a temporary folder, then unpack it with `tar`). If only names or dates differ, say so and carry on. If the terms differ, go as far as the major before it and stop, even if they asked for the later one, because they can't have agreed to a license they haven't seen. Quote the new license's own words, and ask whether to accept it, stay where they are, or replace the library. If they accept, carry on with that major. Don't add a key or look for a way around the license's check. Check the library's companion packages too, such as `@primeuix/themes` with PrimeNG, because the plan only compares what it moves. If they only asked a question, tell them in your answer. -- **`Node: too-old` on a major:** `ng` will refuse to run. Stop before that major and ask them to switch Node. Don't switch it yourself, since `nvm use` changes it for the whole machine on Windows. `unsupported` means Node is newer than Angular lists: mention it and carry on while the checks pass. +- **`Node: too-old` on a major:** Angular doesn't support this Node there: `ng` will refuse to run, or on some odd majors, such as 25, only warn. Stop before that major and ask them to switch to a Node in the range the plan prints, which can be a newer release of the line they're on, such as 24.15 for 24.14. Don't switch it yourself, since `nvm use` changes it for the whole machine on Windows. `unsupported` means Node is newer than every version Angular lists: mention it and carry on while the checks pass. - **An Nx workspace:** the plan says so. Use `nx migrate` for the framework packages, and the plan for the library facts. +- **A workspace of libraries the person publishes,** built with `ng-packagr`, often with no app to serve: read `references/libraries.md` too, because each library's own peer ranges and version have to move as well, and publishing waits for their go-ahead. - **They asked to try a prerelease** (an RC, Angular's `next`, or a major the plan says has a prerelease out): plan with `--next`, and only then, because nothing else should land on a version that isn't meant for production. The rest works the same way, one committed major at a time. Say in your report that it's a prerelease, and which libraries only accept it through a prerelease of their own. Before changing anything, if you're on the default branch, create `upgrade/angular-`, named for the last major this run will commit: the target, or where the plan's ceiling, a license change or Node stops it. On any other branch, stay there. Never push. @@ -82,6 +83,8 @@ For pnpm, the command runs `$SCRIPTS/update.mjs` through `pnpm exec node`, and f **e. Finish the major:** - Install anything the plan from step (a) lists under this major that `ng update` didn't move (check `package.json`). A shared package's line says which versions fit: if `ng update` already put it on one of them, keep it. TypeScript usually moves on its own. Other shared packages such as `marked` or `ag-grid-community` often don't, and nor does a peer a library newly needs, listed as `not installed`, which yarn won't add for you. +- On pnpm, `pnpm add` lets in a release less than a day old by writing it into `minimumReleaseAgeExclude` in `pnpm-workspace.yaml`, and creates that file if it has to. If `git status` shows that, the exemption is the person's call, so ask before committing it. If they say no, undo the whole add (`package.json`, the lockfile and `pnpm-workspace.yaml`) and reinstall, since taking out only the entry leaves the release in the lockfile. +- Raise each Node pin the plan lists under the major (`Node pins below that, in the repo:`, or `Node pins below what Angular needs` when the app is already on it), such as a dev container's image or Dockerfile, `.nvmrc`, `engines` or a CI workflow, to a version in that range, keeping its form, and say so under hand fixes. Those are the team's machines, and they'd fail where this one passes. Don't switch this machine's Node. The plan reads `.nvmrc`, `.node-version`, `.tool-versions`, `package.json`, a `Dockerfile` or `.devcontainer/Dockerfile`, `.devcontainer/devcontainer.json` and GitHub workflows, in the app's folder and at the repo's root, and passes over a version it can't read, such as `lts/iron`. So search the repo for other pins too, such as a compose file, another CI system, a root `.devcontainer.json` or a Dockerfile its `build.dockerfile` names elsewhere, and say in the report where you looked. A pin whose path starts with `../` is outside the app's folder and can belong to another project in the repo, so raise it only when it builds, tests or runs this app, since this app's checks can't catch what the change does to anything else, and say which you left. - For an `angular.json:` line under the major, a file the app loads that the library no longer ships, follow `references/troubleshooting.md` for a file named in `angular.json` that no longer exists. - Do the guide's remaining steps that apply. - Read what each library's new major changed, even if nothing broke: its changelog in `node_modules/`, or when it ships none, the release notes or `CHANGELOG.md` in the repository the plan names, for every major crossed. A build and tests can't see a dropdown that now renders somewhere else, which is what ng-select 24 did when it started requiring `@angular/cdk`. Anything that touches what this app uses goes in your report, and so does a library `ng update` took to a later major than the plan named. @@ -97,7 +100,7 @@ If a failure won't yield to a reasonable effort, stop and ask. Say what you trie ## 5. Stop at the ceiling and ask -When the plan is blocked, the highest reachable major is the end of this run, however close the next one looks. Read `references/blocked.md`, gather the facts it lists, and put the choice to the person: **wait** for the library, **replace** it, or **force** past it, with your recommendation and why. End your message with that question. +When the plan is blocked, the highest reachable major is the end of this run, however close the next one looks. Read `references/blocked.md`, gather the facts it lists, and put the choice to the person: **wait** for the library, **replace** it, or **force** past it, or, when it's their own library, **upgrade it first**, with your recommendation and why. End your message with that question. Don't try a prerelease, an `overrides` entry or `--force` to see if it works. These change what the project depends on in ways that are easy to miss in review, so the person decides first. @@ -109,7 +112,7 @@ Keep it short and lead with the result: - **Commits:** one line per major. - **Hand fixes:** what you changed yourself and why. This is what needs a reviewer. - **Library changes:** what each library's new major changed that touches this app, any peer it newly requires, and any library `ng update` took further than the plan. Never leave this out: when nothing applies, say what you read. -- **Node:** the range the new Angular needs, if it's stricter than before. Their CI and deployment images need it too, even if this machine is fine. +- **Node:** the range the new Angular needs, if it's stricter than before, and any pin you raised. Their CI, deployment and dev container images need it too, even if this machine is fine. - **Unconfirmed:** libraries npm couldn't vouch for, and what you checked instead. - **Already failing:** anything from step 3, left as it was. - **Available but not done:** the optional migrations the last major's `ng update` still offers that would change this app (search the code for what each one targets), settings the migrations added to keep the old behaviour (such as `ChangeDetectionStrategy.Eager`), and deprecations to plan for. A deprecated package nothing imports only needs removing. @@ -124,3 +127,4 @@ An upgrade diff is already hard to review, and changes nobody asked for are the - **No unrelated bumps or cleanups:** no `npm audit fix`, no newer versions of libraries the plan leaves alone, no reformatting. - **No `--force`, `--legacy-peer-deps`, `overrides`, `resolutions`, `peerDependencyRules` or prereleases** without the person's approval. They hide the conflict the plan exists to surface. - **No approving a package's install script** without the person's approval. It runs that package's code on every install. +- **No release-age exemption** (`minimumReleaseAgeExclude`, `npmPreapprovedPackages`) without the person's approval. It lets in a release too new for a compromise to have been caught. diff --git a/plugins/ng-hop/skills/ng-hop/references/blocked.md b/plugins/ng-hop/skills/ng-hop/references/blocked.md index 17f8983..19faff3 100644 --- a/plugins/ng-hop/skills/ng-hop/references/blocked.md +++ b/plugins/ng-hop/skills/ng-hop/references/blocked.md @@ -10,9 +10,12 @@ For each blocker, from the plan and a quick search: - **How much the app uses it:** count the files that import it (for example `git grep -l "" -- src`). Three files and three hundred are different decisions. - **Whether it's used at all.** If nothing imports it, removing it clears the blocker, and that's your recommendation. Search for its imports and selectors across `src` instead of reading a few files, because a template can sit inside a component's `.ts` file. - **Any sign of support on the way:** a prerelease in the plan's output, or an open issue or pull request on its repository. Say what you found, and say so if you didn't look. +- **Whether it's the person's own.** The plan says when a blocker's scope has a registry of its own in their npm config, and in which order to upgrade several. That's how a company publishes its own libraries, but it's also how a vendor ships a paid one: Font Awesome Pro's customers point `@fortawesome` at Font Awesome's registry, and Infragistics' point `@infragistics` at theirs. So confirm it's the team's before recommending they upgrade it first: its repository and author (`npm view repository.url author`) should be theirs, and if that doesn't settle it, ask. Their own library's source is usually in a repository of its own, so ask where. ## The choices +**Upgrade it first.** When the blocker is the team's own library, waiting would only wait on themselves. Work in that library's repository, with this skill and `references/libraries.md`, to the Angular major the plan names, in the order it gives, and publish once they say so. Libraries the plan says need each other have no order between them, so upgrade and publish them together. Then come back to the app and run the plan again. Recommend this for their own libraries, once you've confirmed they are, and say which needs which. A vendor's library behind a registry of its own is a maintained library like any other, so for one of those, waiting is usually right. + **Wait.** Stay on the ceiling, with the work committed, and run the plan again later. It costs nothing and the app is already further along than it was. This is the usual recommendation for a maintained library, especially when the Angular major is recent. **Replace.** Move to a library that supports the target, or remove the need for one. This is a real project of its own, so don't start it unprompted. Before suggesting a replacement, check that it actually supports the target: put it in a scratch `package.json` and run the plan on that, or read its release's peer range. Recommend this when the library looks abandoned or is deprecated on npm, or when the app barely uses it. @@ -27,6 +30,10 @@ Name the blocker, give your recommendation and the reason, and ask. For example: > You're on Angular 21 now, committed and passing. `@nebular/theme` is the one thing blocking 22: its latest release is from January and requires Angular 21, and 14 files import it. I'd wait, because it has had a release for every Angular major so far. Do you want to wait, replace it, or try forcing it? +Or, when the blockers are their own: + +> This app is on Angular 21, and only your own libraries block 22: `@acme/ui-core` and `@acme/ui-forms`, both from your registry, both requiring Angular 21. I'd upgrade them first, `@acme/ui-core` and then `@acme/ui-forms`, which needs it, publish both, and then come back for this app. Where is their repository, and shall I start there? + ## Only if they choose to force Do it as its own commit, so it's easy to revert and easy to see in review. diff --git a/plugins/ng-hop/skills/ng-hop/references/libraries.md b/plugins/ng-hop/skills/ng-hop/references/libraries.md new file mode 100644 index 0000000..76b9274 --- /dev/null +++ b/plugins/ng-hop/skills/ng-hop/references/libraries.md @@ -0,0 +1,26 @@ +# Upgrading a library you publish + +A workspace of Angular libraries goes up a major the way an app does, through `ng update` one major at a time. Two things an app doesn't have move with it: each library's own peer ranges and version, and the publish that lets the apps using it follow. + +## What's different in a library workspace + +- **The plan and `ng update` work as for an app.** They read the workspace's `package.json`, which holds Angular and the build tools. Each library's `projects//package.json` is what apps install, and nothing moves it for you. +- **There may be no app to run.** `smoke.mjs` needs a `serve` target. In a workspace of libraries only, the build and the tests are the checks, and the apps that use the libraries are where they really run, so say that the app check didn't apply. +- **Build and test with the workspace's own scripts.** With several libraries, a plain `ng build` stops with `Cannot determine project for command.`, and a library that imports another from the same workspace builds against it from `dist`, so the one it needs has to be built first, for its tests too. For the starting point and every check, use the workspace's build and test scripts, or `npx --no-install ng build ` for each in dependency order before `npx --no-install ng test`, the same way before and after, and say which in the report. + +## Move each library to the new major + +After the workspace's `ng update`, for each library: + +1. **Its Angular peers:** move every `@angular/*` peer range to the new major, such as `^22.0.0`. A library built with Angular 22's compiler isn't promised to work on 21, so move the range rather than widening it. +2. **Its peers and dependencies on the team's other libraries:** move them to the releases you're about to publish, such as `"@acme/ui-core": "^22.0.0"`. +3. **Its version:** a new major, since dropping the old Angular breaks its users. Follow how its history numbers releases (many internal libraries match Angular's major), and ask if that isn't clear. +4. **Its changelog,** if it keeps one. + +Then build every library, run their tests and commit, as for any major. Say under hand fixes which ranges and versions you moved. + +## Publishing waits for the person + +Publishing changes what every app using the library installs, and a version number can't be published twice. So stop before it. Name each package and version, the commit they come from, on a branch nobody has pushed or reviewed yet, and the registry each would go to, as a dry run prints it from where the publish runs, usually the workspace root: `pnpm publish --dry-run --no-git-checks` (pnpm's own check refuses any branch but main or master by default, even for a dry run) or `npm publish --dry-run`. The registry can come from the workspace's `.npmrc` or from the library's own `publishConfig`, and pnpm and npm disagree on which wins. Run from inside the dist folder, neither reads the workspace's `.npmrc` at all, so a library with no `publishConfig` would go to the public registry. Then ask. Once they say yes, build from that commit and use the workspace's own publish script if it has one, or publish them one at a time in the order the app's plan gave. + +Then go back to the app and run the plan again. The app moves one major at a time, so it needs a release for each major it crosses. When it's more than one behind, take the libraries only to the major the app's plan names (`--to `), publish, move the app, and repeat. On pnpm 11 or newer, or yarn 4.15 or newer, `ng update` from Angular CLI 22.1 on skips a release less than a day old and quotes the old release's peers: `references/troubleshooting.md` covers it. diff --git a/plugins/ng-hop/skills/ng-hop/references/troubleshooting.md b/plugins/ng-hop/skills/ng-hop/references/troubleshooting.md index 1f62d3b..b6bc026 100644 --- a/plugins/ng-hop/skills/ng-hop/references/troubleshooting.md +++ b/plugins/ng-hop/skills/ng-hop/references/troubleshooting.md @@ -30,9 +30,30 @@ Something in the project needs an older Angular, and it isn't in the command. - Re-run `plan.mjs`. If it names `x` with a version, you ran an out-of-date command, so run the new one. - If `x` isn't a direct dependency, it's pulled in by one. Find the parent with `npm ls x` (`pnpm why x` on pnpm) and treat the parent as the library to move. - If no release of `x` supports this major, it's a blocker: go to `blocked.md`. +- If the command names `x` at a release that does support this major, and the error still quotes the old release's peers, `ng update` is most likely skipping a release less than a day old, as Angular CLI 22.1 and newer do on pnpm 11 or newer, or yarn 4.15 or newer: see the next section. Don't reach for `--force`. It only skips this check, and the same mismatch comes back at install time or at runtime. +## A release published minutes ago is held back on pnpm + +``` +Package "@acme/ui-core" has an incompatible peer dependency to "@angular/common" (requires "^21.2.0", would install "22.2.5"). +``` + +This shows when the command names a release that does accept the new Angular, such as `@acme/ui-core@22.0.0`, while that release is less than a day old. From Angular CLI 22.1, `ng update` skips any release younger than the package manager's minimum release age, a guard against a compromised release, so it keeps the installed release and quotes that one's peers. pnpm 11 and newer set that age to 1440 minutes by default, and `pnpm config get minimumReleaseAge` prints `undefined` for the default, so confirm the cause with the release's age from `npm view time`. yarn does the same with `npmMinimalAgeGate`, which defaults to a day from yarn 4.15, and `yarn config get npmMinimalAgeGate` does print it, in minutes. + +`ng update` reads no exemption list, so an exemption alone doesn't get the release past it. The package managers behave differently: + +- **pnpm** installs a release it's asked for by name, and writes the exemption into `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` itself, creating the file if it has to: `Added 1 entry to minimumReleaseAgeExclude in pnpm-workspace.yaml`. With `minimumReleaseAgeStrict: true` it stops with `ERR_PNPM_NO_MATURE_MATCHING_VERSION` instead. +- **yarn** refuses the release, with `YN0016: │ @acme/ui-core@npm:^22.0.0: All versions satisfying "^22.0.0" are quarantined`, until `.yarnrc.yml` lists it under `npmPreapprovedPackages`. + +Exempting a release relaxes that guard, so it's the person's call, even for their own package: ask. They have two ways on. + +- **Wait** until the release is a day old, then re-run the plan and its command. It costs a day and nothing else. +- **Install it first,** only for a library the plan doesn't mark `ships migrations`. `ng update` skips migrations for a version `package.json` already claims, so for a library that ships them, wait. With their go-ahead, add each release to the exemption list (`- '@acme/ui-core@22.0.0'` under `minimumReleaseAgeExclude`, or under `npmPreapprovedPackages`), or the team's whole scope (`'@acme/*'`) once if they trust their registry, and commit that by itself. It changes no installed package, so it's safe to stop on. Then run the plan's `Run:` command with `--install-first ` for each of those libraries. `update.mjs` installs them first, in the form `package.json` gives them now, checks that only `package.json` and the lockfile changed, and then updates the rest, all in the one commit for that major. Say in the report that the update ran that way, and why. + +If `update.mjs` stops with `Installing first also changed pnpm-workspace.yaml`, pnpm wrote an exemption the list didn't cover, such as one for a release newer than the one approved. Put the tree back with `git restore :/`, which also covers a workspace root above the app, where pnpm writes to the root's `pnpm-workspace.yaml` and lockfile. If `git status` shows `pnpm-workspace.yaml` as untracked, pnpm made it beside `package.json` because there was none, so delete it. Then reinstall with `pnpm install --frozen-lockfile`, and ask about that release. + ## The plan rejects angular-eslint with "requires no Angular version" An older planner could report this despite a current, target-aligned builder release: @@ -51,7 +72,7 @@ Read which two requirements conflict. Usually a shared package such as `marked` ## `Repository is not clean` -Something changed since the last commit. Finish and commit the major in progress, or find out what changed. `--allow-dirty` belongs only to the `--migrate-only` case above. +Something changed since the last commit. Finish and commit the major in progress, or find out what changed. `--allow-dirty` belongs only to the `--migrate-only` case above. `update.mjs --install-first` passes it to `ng update` itself, once it has checked that its own install changed only `package.json` and the lockfile. If the repository was clean immediately before pnpm printed this sequence from a plain `pnpm exec ng update`: @@ -83,7 +104,7 @@ Error: ERR_PNPM_IGNORED_BUILDS pnpm 11 and newer won't run a dependency's install script until the project has decided about it, and every install exits 1 until each one is decided. Inside `ng update` this only shows as `✖ Installing packages [FAILED: Unable to install packages]`, with no migrations run, so run `pnpm install` yourself to see the reason. pnpm also writes `set this to true or false` placeholders into `pnpm-workspace.yaml`, which dirties the tree and must never be committed. -1. Put the tree back: `git restore .`, and delete `pnpm-workspace.yaml` if `git status` shows it as untracked, since pnpm just made it. +1. Put the tree back: `git restore :/`, which also covers a workspace root above the app, and delete `pnpm-workspace.yaml` if `git status` shows it as untracked, since pnpm just made it. 2. Decline each package the error names, by adding it to `pnpm-workspace.yaml` as `false` and keeping any entries already there: ```yaml allowBuilds: @@ -100,7 +121,7 @@ In your report, list what you declined and say that `pnpm approve-builds { const scope = name.startsWith('@') ? name.split('/')[0] : null; @@ -69,10 +70,23 @@ function registryClient(config, cwd) { return url.endsWith('/') ? url : `${url}/`; }; - // npm holds any private-registry credentials, so an auth failure on the direct path falls back to it. + // npm holds any private-registry credentials, so an auth failure on the direct path falls back to it, told the registry + // the plan chose. npm reads one folder's .npmrc, and a pnpm workspace's token can be in the root's or the app's, so an + // auth failure in one folder tries the next. --workspaces=false keeps npm in the folder it's given. + async function npmView(name, spec, maxBuffer) { + const scope = name.startsWith('@') ? `${name.split('/')[0]}:` : ''; + for (const [i, cwd] of dirs.entries()) { + try { + return await runNpm(['view', spec, '--json', `--${scope}registry=${baseFor(name)}`, '--workspaces=false'], { cwd, maxBuffer }); + } catch (err) { + if (i === dirs.length - 1 || !/E40[13]/.test(`${err.stdout || ''}${err.message || ''}`)) throw err; + } + } + } + async function viaNpm(name) { try { - const { stdout } = await runNpm(['view', `${name}@>=0.0.0-0`, '--json'], { cwd, maxBuffer: 512 * 1024 * 1024 }); + const { stdout } = await npmView(name, `${name}@>=0.0.0-0`, 512 * 1024 * 1024); const docs = [].concat(JSON.parse(stdout)); const versions = Object.fromEntries(docs.map((d) => [d.version, d])); return { name, versions, time: docs[0]?.time || {}, 'dist-tags': docs[0]?.['dist-tags'] || {} }; @@ -85,7 +99,7 @@ function registryClient(config, cwd) { async function viaNpmRelease(name, version) { try { - const { stdout } = await runNpm(['view', `${name}@${version}`, '--json'], { cwd, maxBuffer: 64 * 1024 * 1024 }); + const { stdout } = await npmView(name, `${name}@${version}`, 64 * 1024 * 1024); const found = [].concat(stdout.trim() ? JSON.parse(stdout) : []); return found[found.length - 1] || null; } catch (err) { @@ -118,7 +132,17 @@ function registryClient(config, cwd) { const get = (name, full = false) => cached(`${name}|${full}`, () => fetchOnce(baseFor(name) + name.replace('/', '%2F'), full ? 'application/json' : 'application/vnd.npm.install-v1+json', () => viaNpm(name))); // One release's manifest, a few kilobytes, where a whole document can run to megabytes. const release = (name, version) => cached(`${name}@${version}`, () => fetchOnce(`${baseFor(name)}${name.replace('/', '%2F')}/${encodeURIComponent(version)}`, 'application/json', () => viaNpmRelease(name, version))); - return { get, release }; + // A scope with a registry of its own, apart from the default one, is how a company publishes its packages, and how a + // vendor ships licensed ones. A mirror set as the default registry for everything says nothing about who owns a + // package, so only a scope's own counts. + const own = (name) => { + const scope = name.startsWith('@') ? name.split('/')[0] : null; + const base = (url) => (url.endsWith('/') ? url : `${url}/`); + const url = scope && config[`${scope}:registry`]; + return url && base(url) !== base(config.registry || 'https://registry.npmjs.org/') ? base(url) : null; + }; + + return { get, release, own }; } async function mapLimited(items, fn) { @@ -229,6 +253,80 @@ function readBunLock(dir) { } } +// Where the repo pins the Node its team runs: version files, package.json, a dev container or Dockerfile, CI workflows. +// The upgrade passes on this machine and then fails on theirs, so each is checked against every major's Node range. +// In the enterprise run, an agent searched JSON and YAML for pins and missed the dev container's Dockerfile. +function nodePins(dir) { + let root = dir; + while (!existsSync(join(root, '.git')) && dirname(root) !== root) root = dirname(root); + if (!existsSync(join(root, '.git'))) root = dir; + const pins = []; + const read = (path) => { try { return readFileSync(path, 'utf8'); } catch { return null; } }; + const add = (path, value, kind = 'version') => pins.push({ file: relative(dir, path).replace(/\\/g, '/'), value: String(value).trim(), kind }); + const bases = [...new Set([dir, root])]; + for (const base of bases) { + for (const name of ['.nvmrc', '.node-version']) { const text = read(join(base, name)); if (text?.trim()) add(join(base, name), text.trim().split('\n')[0]); } + // asdf writes nodejs, and mise reads node too. + const tools = /^node(?:js)?\s+(\S+)/m.exec(read(join(base, '.tool-versions')) || ''); + if (tools) add(join(base, '.tool-versions'), tools[1]); + for (const docker of ['.devcontainer/Dockerfile', 'Dockerfile']) { + const text = read(join(base, docker)) || ''; + // docker init writes the version as an ARG's default: ARG NODE_VERSION=20.11.0, then FROM node:${NODE_VERSION}-alpine. + const args = new Map([...text.matchAll(/^\s*ARG\s+(\w+)=["']?([^\s"']+)/gim)].map(([, key, value]) => [key, value])); + for (const [, image] of text.matchAll(/^\s*FROM\s+(?:--\S+\s+)*(\S+)/gim)) add(join(base, docker), image.replace(/\$\{?(\w+)\}?/g, (m, key) => args.get(key) ?? m), 'image'); + } + // devcontainer.json allows comments, and a commented-out image is no pin and mustn't hide the one in use. + const devcontainer = (read(join(base, '.devcontainer', 'devcontainer.json')) || '').replace(/^[ \t]*\/\/.*$/gm, ''); + const image = /"image"\s*:\s*"([^"]+)"/.exec(devcontainer)?.[1]; + if (image) add(join(base, '.devcontainer', 'devcontainer.json'), image, 'image'); + const feature = /features\/node:\d+"\s*:\s*\{[^}]*"version"\s*:\s*"([^"]+)"/.exec(devcontainer)?.[1]; + if (feature) add(join(base, '.devcontainer', 'devcontainer.json'), feature); + // A workspace keeps engines and volta at its root too, and Volta's members extend the root's. + try { + const pkg = JSON.parse(read(join(base, 'package.json')) || '{}'); + if (pkg.engines?.node) add(join(base, 'package.json'), pkg.engines.node, 'engines'); + if (pkg.volta?.node) add(join(base, 'package.json'), pkg.volta.node); + } catch { /* a broken package.json pins nothing, and readProject reports the app's */ } + } + const workflows = join(root, '.github', 'workflows'); + for (const name of existsSync(workflows) ? readdirSync(workflows).filter((f) => /\.ya?ml$/.test(f)).sort() : []) { + // A comment line goes with its line break, and one after a value goes too, so neither ends a list of versions. + const text = (read(join(workflows, name)) || '').replace(/\r/g, '').replace(/^[ \t]*#.*\n?/gm, '').replace(/[ \t]+#.*$/gm, ''); + // setup-node takes node-version, or a matrix key through ${{ matrix. }}, each as 20, [20, 22] or one "- 20" a + // line. A matrix key is read only inside a matrix, since outside it a "version" can be pnpm/action-setup's. + const matrix = [...text.matchAll(/^([ \t]*)matrix:(.*\n(?:\1[ \t]+.*\n?|[ \t]*\n)*)/gm)].map((m) => m[2]).join('\n'); + const keys = new Set([...text.matchAll(/node-version:\s*\$\{\{\s*matrix\.([\w-]+)\s*\}\}/g)].map((m) => m[1])); + keys.delete('node-version'); + for (const [source, key] of [[text, 'node-version'], ...[...keys].map((k) => [matrix, k])]) { + for (const [, value] of source.matchAll(new RegExp(`(?=24" allows Node 24.0, which Angular 22 refuses, so the lowest Node an engines range allows has to be accepted. + // "*", which semver also makes of ">=0", allows any Node and pins none. + if (pin.kind === 'engines') { const valid = semver.validRange(pin.value), min = valid && valid !== '*' && semver.minVersion(valid); return !min || semver.satisfies(min, range); } + // A dev container image's tag can start with the image's own version, as typescript-node:5.2-24-bookworm does, so + // the Node in it is the two-digit major after that, and a tag that is only the image's version (5-trixie) says none. + // shortcut: an image's own version freezes the Node inside it, so only its major is known from the tag; read the + // image's NODE_VERSION if a frozen pin below the range's floor ever matters. + const version = pin.kind === 'image' ? /(?:^|[/:])(?:node:|(?:typescript|javascript)-node:(?:\d+(?:\.\d+){0,2}-)?(?=[1-9]\d))(\d+(?:\.\d+){0,2})(?:[-@]|$)/.exec(pin.value)?.[1] : /^v?(\d+(?:\.\d+){0,2})(?:\.x)?$/.exec(pin.value)?.[1]; + if (!version) return true; + const parts = version.split('.'); + const asRange = parts.length === 3 ? version : `${version}.x`; + return semver.intersects(asRange, range); + } catch { + return true; // a number semver can't hold, past 2^53, says nothing about the team's Node + } +} + // The files angular.json loads straight from node_modules, by package: the scripts and styles of every target and // configuration. A library's new major can stop shipping one, as marked 16 did with marked.min.js, and until now only // the build said so. @@ -534,9 +632,20 @@ async function broughtLicenses(registry, lib, before, after) { async function buildPlan({ projectDir, to, next = false }) { const project = readProject(projectDir); const assets = angularAssets(projectDir); - const { stdout: configText } = await runNpm(['config', 'list', '--json'], { cwd: projectDir }) + const pins = nodePins(projectDir); + // pnpm reads a workspace's registry settings from the .npmrc at its root, and pnpm 10 and older lay the app's own over + // it, where npm reads one folder's. So npm is asked at the root, then for the keys the app's own file sets, since its + // listing holds every default too. --workspaces=false keeps npm in the app's folder below a root that declares them. + const npmConfig = (cwd, args = []) => runNpm(['config', 'list', '--json', ...args], { cwd }).then(({ stdout }) => JSON.parse(stdout)) .catch((err) => { throw new PlanError(`Could not run npm to read the registry settings: ${err.message.split('\n')[0]}`); }); - const registry = registryClient(JSON.parse(configText), projectDir); + const configDir = project.pnpmRoot || projectDir; + const config = await npmConfig(configDir); + const appRc = join(projectDir, '.npmrc'); + if (configDir !== projectDir && existsSync(appRc)) { + const appConfig = await npmConfig(projectDir, ['--workspaces=false']); + for (const [, key] of readFileSync(appRc, 'utf8').matchAll(/^\s*([^#;\s=[][^=]*?)\s*=/gm)) if (Object.hasOwn(appConfig, key)) config[key] = appConfig[key]; + } + const registry = registryClient(config, [...new Set([configDir, projectDir])]); // Through Angular 21's CLI, `ng update` prints "Package x was not found on the registry. Skipping." for these and // still exits 0, and Angular 22's stops with an error, so the pnpm runner moves the catalog itself. @@ -752,7 +861,10 @@ async function buildPlan({ projectDir, to, next = false }) { let node = { range: nodeRange, status: 'unknown' }; if (nodeRange && semver.validRange(nodeRange)) { const version = process.version.replace(/^v/, ''); - node.status = semver.satisfies(version, nodeRange) ? 'ok' : semver.ltr(version, nodeRange) ? 'too-old' : 'unsupported'; + // A Node in a gap of the range, as 24.14 is in Angular 22's, is too old: a newer one fits, and ng won't start on it. + node.status = semver.satisfies(version, nodeRange) ? 'ok' : semver.gtr(version, nodeRange) ? 'unsupported' : 'too-old'; + const below = pins.filter((p) => !pinFits(p, nodeRange)).map(({ file, value }) => ({ file, value })); + if (below.length) node.pins = below; } const moved = libs.filter((l) => l.from !== l.to); @@ -853,6 +965,47 @@ async function buildPlan({ projectDir, to, next = false }) { } } + // A blocker from a scope the npm config gives a registry of its own may be the team's library, which can be upgraded + // first rather than waited for. A licensed vendor's registry is set up the same way, so the plan says only what the + // config shows. When several are the team's, one can need another, and the one it needs has to be published first. + const own = plan.blockers.filter((b) => b.major === blockedAt && registry.own(b.name)); + if (own.length) { + for (const b of own) { b.ownRegistry = registry.own(b.name); b.reason += `; your npm config sets a registry for ${b.name.split('/')[0]}, ${b.ownRegistry}`; } + const names = new Set(own.map((b) => b.name)); + // An optional peer is one a library works with, not one it needs. + const needs = new Map(own.map((b) => { + const manifest = docs.get(b.name)?.versions?.[b.installed || b.latest] || {}; + return [b.name, Object.keys({ ...manifest.dependencies, ...manifest.peerDependencies }) + .filter((n) => names.has(n) && n !== b.name && (manifest.dependencies?.[n] || !manifest.peerDependenciesMeta?.[n]?.optional))]; + })); + const order = []; + // Every library n needs, directly or through others, that isn't in the order yet. + const reach = (n, seen = new Set()) => { for (const d of needs.get(n)) if (!order.includes(d) && !seen.has(d)) { seen.add(d); reach(d, seen); } return seen; }; + // Before anything is in the order, that's every library n needs. + const all = new Map([...names].map((n) => [n, reach(n)])); + // A library and the ones that need it back go into the order as one group, so one that only needs them comes after + // all of them, not between them. + const cycle = (n) => [...names].filter((m) => m === n || (all.get(n).has(m) && all.get(m).has(n))); + const groups = []; + for (const n of [...names].sort()) if (!groups.some((g) => g.includes(n))) groups.push(cycle(n)); + while (order.length < names.size) { + for (const g of groups.filter((g) => !order.includes(g[0]) && g.every((m) => needs.get(m).every((d) => order.includes(d) || g.includes(d))))) { + while (g.some((m) => !order.includes(m))) { + const ready = g.filter((m) => !order.includes(m) && needs.get(m).every((d) => order.includes(d))).sort(); + // Inside a group nothing may be ready. One whose every need leads back to it starts. + order.push(...(ready.length ? ready : [g.find((m) => !order.includes(m) && [...reach(m)].every((d) => reach(d).has(m)))])); + } + } + } + plan.ownBlockers = order; + const each = order.map((n) => (needs.get(n).length ? `${n}, which needs ${needs.get(n).join(' and ')}` : n)); + // Libraries that need each other, directly or through others, go out together, however many there are. + const groupOf = (n) => order.filter((m) => all.get(n).has(m) && all.get(m).has(n)); + const together = order.filter((n) => groupOf(n)[0] === n).map(groupOf).map((g) => `${g.slice(0, -1).join(', ')} and ${g.at(-1)}`); + const scopes = [...new Set(order.map((n) => n.split('/')[0]))]; + const several = order.length > 1; + plan.notes.push(`${several ? `${order.slice(0, -1).join(', ')} and ${order.at(-1)} come` : `${order[0]} comes`} from the ${scopes.length > 1 ? 'registries' : 'registry'} your npm config sets for ${scopes.join(' and ')}. If ${several ? 'they are' : 'it is'} your team's, ${several ? 'each' : 'it'} can be upgraded first, with a release that accepts Angular ${blockedAt}${several ? `, in this order: ${each.join(', then ')}` : ''}.${together.length ? ` ${together.join(', and ')} need each other, so no order works for them: upgrade and publish them together.` : ''}`); + } if (blockedAt !== null) plan.status = 'blocked'; else if (target === currentMajor) { plan.status = 'already-there'; @@ -867,6 +1020,11 @@ async function buildPlan({ projectDir, to, next = false }) { : semver.maxSatisfying(eligibleReleases(doc, have), `>=${currentMajor}.0.0 <${currentMajor + 1}.0.0`); if (newest && semver.gt(newest, have)) behind.push({ name, from: have, to: newest }); } + // A pin the last upgrade left behind still fails the team, so it's named here too, against this major's range. + const installed = project.installed('@angular/core')?.version; + const range = installed && (await registry.release('@angular/core', installed).catch(() => null))?.engines?.node; + const below = range && semver.validRange(range) ? pins.filter((p) => !pinFits(p, range)).map(({ file, value }) => ({ file, value })) : []; + if (below.length) plan.nodePins = { range, pins: below }; if (behind.length) { // Only a CLI that has to move bootstraps a temporary copy of itself. const update = behind.some((b) => b.name === '@angular/cli') ? updateCommand(project) : `${runnerFor(project)} ng update`; @@ -905,6 +1063,7 @@ function render(plan) { for (const b of plan.patches.packages) lines.push(` ${b.name}: ${b.from} -> ${b.to}`); lines.push(` Run: ${plan.patches.command}`); } + if (plan.nodePins) lines.push(` Node pins below what Angular ${plan.target} needs (${plan.nodePins.range}), in the repo: ${plan.nodePins.pins.map((p) => `${p.file} (${p.value})`).join(', ')}`); } else if (plan.status === 'ok') lines.push(`Result: Angular ${plan.next && semver.major(plan.next) === plan.target ? `${plan.next}, a prerelease,` : plan.target} is reachable.`); else lines.push(plan.reachable === semver.major(p.angular) @@ -914,6 +1073,7 @@ function render(plan) { for (const hop of plan.hops) { lines.push('', `Angular ${hop.major} (${hop.angular})`); lines.push(` Node: ${hop.node.status}${hop.node.range ? ` (needs ${hop.node.range})` : ''}`); + if (hop.node.pins) lines.push(` Node pins below that, in the repo: ${hop.node.pins.map((p) => `${p.file} (${p.value})`).join(', ')}`); for (const lib of hop.libs) { const move = lib.from === lib.to ? `${lib.to}, no change` : `${lib.from ?? 'not installed'} -> ${lib.to}`; lines.push(` ${lib.name}: ${move}; ${SUPPORT[lib.support]}${lib.peer ? ` (${lib.peer})` : ''}${lib.migrations ? '; ships migrations' : ''}${lib.newPeers ? `; newly needs ${lib.newPeers.slice(0, 8).join(', ')}${lib.newPeers.length > 8 ? ` and ${lib.newPeers.length - 8} more` : ''}, not in package.json` : ''}${lib.from === lib.to && lib.newer ? `; ${lib.newer} is newer, with an open-ended range too, if the app needs it` : ''}${lib.deprecated ? `; DEPRECATED on npm: "${String(lib.deprecated).slice(0, 120)}"` : ''}${lib.repository ? `; repository: ${lib.repository}` : ''}`); diff --git a/plugins/ng-hop/skills/ng-hop/scripts/update.mjs b/plugins/ng-hop/skills/ng-hop/scripts/update.mjs index c1fa0af..5d8b260 100644 --- a/plugins/ng-hop/skills/ng-hop/scripts/update.mjs +++ b/plugins/ng-hop/skills/ng-hop/scripts/update.mjs @@ -3,38 +3,39 @@ import { spawnSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { createRequire } from 'node:module'; -import { delimiter, dirname, join, resolve } from 'node:path'; +import { delimiter, dirname, join, relative, resolve } from 'node:path'; import { parseArgs } from 'node:util'; const semver = createRequire(import.meta.url)('./vendor/semver.cjs'); const VALID_NAME = /^(@[a-z0-9~-][a-z0-9._~-]*\/)?[a-z0-9~-][a-z0-9._~-]*$/i; -const USAGE = 'Usage: update.mjs [--project ] [--json] @angular/core@ @angular/cli@ [library@version ...]\nRuns the planned pnpm hop or patch once. With catalog: entries in package.json, it moves the catalog around the update. No force flags, retries, resets, commits or manager provisioning.'; +const USAGE = 'Usage: update.mjs [--project ] [--json] [--install-first ...] @angular/core@ @angular/cli@ [library@version ...]\nRuns the planned pnpm or yarn hop or patch once. With catalog: entries in package.json, it moves the catalog around the update. With --install-first, it installs those libraries first, in the form package.json gives them, and then updates the rest. No force flags, retries, resets, commits or manager provisioning.'; // Node cannot exec a Windows .cmd file without a shell. Read only the standard shim's literal script path instead. -function pnpmCommand(cwd) { +// pnpm runs a whole hop this way, and yarn only installs a library first. +function managerCommand(cwd, name) { const execpath = process.env.npm_execpath && resolve(cwd, process.env.npm_execpath); if (execpath && existsSync(execpath)) { - if (/[\\/]pnpm[^\\/]*\.[cm]?js$/i.test(execpath)) return { file: process.execPath, prefix: [execpath] }; + if (new RegExp(String.raw`[\\/]${name}[^\\/]*\.[cm]?js$`, 'i').test(execpath)) return { file: process.execPath, prefix: [execpath] }; // pnpm 12 ships a native binary, and `pnpm exec` names it here. - if (/[\\/]pnpm(?:-[a-z]+)?(?:\.exe)?$/i.test(execpath)) return { file: execpath, prefix: [] }; + if (new RegExp(String.raw`[\\/]${name}(?:-[a-z]+)?(?:\.exe)?$`, 'i').test(execpath)) return { file: execpath, prefix: [] }; } - const corepack = process.env.COREPACK_ROOT && join(process.env.COREPACK_ROOT, 'dist', 'pnpm.js'); + const corepack = process.env.COREPACK_ROOT && join(process.env.COREPACK_ROOT, 'dist', `${name}.js`); if (corepack && existsSync(corepack)) return { file: process.execPath, prefix: [corepack] }; const path = Object.entries(process.env).find(([key]) => key.toLowerCase() === 'path')?.[1] || ''; const extensions = process.platform === 'win32' ? ['.exe', '.com', '.cmd', '.bat'] : ['']; for (const dir of path.split(delimiter).filter(Boolean)) for (const extension of extensions) { - const file = resolve(cwd, dir, 'pnpm' + extension); + const file = resolve(cwd, dir, name + extension); if (!existsSync(file)) continue; if (!/\.(?:cmd|bat)$/i.test(file)) return { file, prefix: [] }; // Corepack's shim names the same script twice, once for each way it finds Node. - const targets = new Set([...readFileSync(file, 'utf8').matchAll(/"%(?:dp0%|~dp0)[\\/]?([^"\r\n%]*pnpm\.[cm]?js)"/gi)].map((m) => m[1])); + const targets = new Set([...readFileSync(file, 'utf8').matchAll(new RegExp(String.raw`"%(?:dp0%|~dp0)[\\/]?([^"\r\n%]*${name}\.[cm]?js)"`, 'gi'))].map((m) => m[1])); if (targets.size === 1) { const target = resolve(dirname(file), [...targets][0].replace(/[\\/]/g, '/')); if (existsSync(target)) return { file: process.execPath, prefix: [target] }; } - throw new Error('Cannot resolve this pnpm shim without a shell. Use the existing Corepack or Node-script installation; no fallback was run.'); + throw new Error(`Cannot resolve this ${name} shim without a shell. Use the existing Corepack or Node-script installation; no fallback was run.`); } - throw new Error('Could not find pnpm. Run the printed pnpm exec command with the already installed project package manager.'); + throw new Error(`Could not find ${name}. Run the printed command with the already installed project package manager.`); } // For yarn, npm runs the target CLI from its own cache, as pnpm's dlx does for pnpm. Asked to update, an older CLI @@ -162,13 +163,13 @@ function redact(text) { } function main() { - const { values, positionals } = parseArgs({ options: { project: { type: 'string' }, json: { type: 'boolean' }, help: { type: 'boolean' } }, allowPositionals: true }); + const { values, positionals } = parseArgs({ options: { project: { type: 'string' }, json: { type: 'boolean' }, help: { type: 'boolean' }, 'install-first': { type: 'string', multiple: true } }, allowPositionals: true }); if (values.help) { console.log(USAGE); return 0; } - const names = new Set(); + const requested = new Map(); for (const spec of positionals) { const at = spec.lastIndexOf('@'), name = spec.slice(0, at), version = spec.slice(at + 1); - if (at < 1 || !VALID_NAME.test(name) || !semver.validRange(version) || names.has(name)) throw new Error('Invalid or duplicate package specifier. Use the exact packages from the plan.'); - names.add(name); + if (at < 1 || !VALID_NAME.test(name) || !semver.validRange(version) || requested.has(name)) throw new Error('Invalid or duplicate package specifier. Use the exact packages from the plan.'); + requested.set(name, version); } // A hop names majors for both. A patch names exact versions, and may only move the CLI. const cli = /^@angular\/cli@(\d+)(?:\.\d+\.\d+)?$/.exec(positionals.find(spec => spec.startsWith('@angular/cli@')) || ''); @@ -177,19 +178,51 @@ function main() { if (!Number.isSafeInteger(major) || major < 2 || (core && semver.coerce(core.slice('@angular/core@'.length))?.major !== major)) throw new Error('The framework requests must name the same target major for @angular/core and @angular/cli.'); const cwd = resolve(values.project || '.'); const yarn = existsSync(join(cwd, 'yarn.lock')); - const runner = yarn ? npmCommand() : pnpmCommand(cwd); - const status = spawnSync('git', ['--no-optional-locks', 'status', '--porcelain', '--untracked-files=all'], { cwd, encoding: 'utf8', windowsHide: true }); - if (status.status !== 0) throw new Error('Could not verify the Git working tree. No update was run.'); - if (status.stdout.trim()) throw new Error('Repository is not clean. Preserve and inspect the changes before another update.'); + // From Angular CLI 22.1, ng update skips a release younger than the package manager's minimum release age and reads + // no exemption list, while an installed release is read from node_modules. So a library the person agreed to take + // that young is installed first, in the form package.json gives it now (^, ~ or exact, as ng update writes it), and + // ng update moves the rest. Never Angular itself, whose migrations only ng update runs. + const firstNames = [...new Set(values['install-first'] || [])]; + const manifest = firstNames.length ? JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8')) : {}; + const first = firstNames.map((name) => { + const version = semver.valid(requested.get(name) ?? ''), now = manifest.dependencies?.[name] ?? manifest.devDependencies?.[name]; + if (!version || name === '@angular/core' || name === '@angular/cli' || typeof now !== 'string' || !semver.validRange(now)) throw new Error(`--install-first takes a library the command names at an exact version, which package.json lists with a version range, and never Angular itself. ${name} isn't one. ${unchanged}`); + return `${name}@${/^[\^~]/.exec(now)?.[0] ?? ''}${version}`; + }); + const runner = yarn ? npmCommand() : managerCommand(cwd, 'pnpm'); + const installer = first.length ? (yarn ? managerCommand(cwd, 'yarn') : runner) : null; + const status = (...paths) => spawnSync('git', ['--no-optional-locks', 'status', '--porcelain', '--untracked-files=all', ...(paths.length ? ['--', ...paths] : [])], { cwd, encoding: 'utf8', windowsHide: true }); + const clean = status(); + if (clean.status !== 0) throw new Error('Could not verify the Git working tree. No update was run.'); + if (clean.stdout.trim()) throw new Error('Repository is not clean. Preserve and inspect the changes before another update.'); const env = { ...process.env }; const controls = { NG_DISABLE_VERSION_CHECK: 'true', NG_CLI_ANALYTICS: 'false', COREPACK_ENABLE_NETWORK: '0', COREPACK_ENABLE_AUTO_PIN: '0', COREPACK_ENV_FILE: '0', npm_config_manage_package_manager_versions: 'false', npm_config_pm_on_fail: 'error' }; for (const key of Object.keys(env)) if (Object.keys(controls).some(owned => owned.toLowerCase() === key.toLowerCase())) delete env[key]; Object.assign(env, controls); + const options = { cwd, env, encoding: 'utf8', windowsHide: true, stdio: values.json ? 'pipe' : 'inherit', maxBuffer: 64 * 1024 * 1024 }; + if (first.length) { + // pnpm keeps the lockfile beside pnpm-workspace.yaml, which can be above the app. + const workspace = yarn ? null : findWorkspaceFile(cwd); + const lockfile = yarn ? 'yarn.lock' : relative(cwd, join(dirname(workspace ?? join(cwd, 'package.json')), 'pnpm-lock.yaml')).replace(/\\/g, '/'); + // pnpm refuses an add in a workspace root that lists two or more package patterns, unless -w says the root is + // meant, as it is here. Below the root, -w would add to the root instead of the app. + const root = workspace === join(cwd, 'pnpm-workspace.yaml') && /^packages\s*:/m.test(readFileSync(workspace, 'utf8')); + const add = spawnSync(installer.file, [...installer.prefix, 'add', ...(root ? ['-w'] : []), ...first], options); + if (add.status !== 0) throw new Error(`Installing ${first.join(' ')} first failed, so no update was run.`); + // The tree was clean a moment ago, so every change since is the install's. Anything past package.json and the + // lockfile, such as a release-age exemption pnpm wrote itself, is the person's to approve, not this hop's to carry. + const after = status(), allowed = status('package.json', lockfile); + const extra = after.stdout.split('\n').filter((line) => line.trim() && !allowed.stdout.split('\n').includes(line)).map((line) => line.slice(3)); + if (after.status !== 0 || allowed.status !== 0 || extra.length) throw new Error(`Installing first also changed ${extra.join(', ') || 'what Git could not report'}, so no update was run. Put the tree back and reinstall before another update.`); + } const inlined = yarn ? null : inlineCatalog(cwd); // Angular's own temporary runner uses this same child-only dispatch control. It does not bypass Git or peers. - // With a catalog, the tree was clean a moment ago and the only change since is the one just made above. - const args = yarn ? ['exec', '--yes', `--package=${cli[0]}`, '--', 'ng', 'update', ...positionals] : ['dlx', cli[0], 'update', ...positionals, ...(inlined ? ['--allow-dirty'] : [])]; - const run = (argv) => spawnSync(runner.file, [...runner.prefix, ...argv], { cwd, env, encoding: 'utf8', windowsHide: true, stdio: values.json ? 'pipe' : 'inherit', maxBuffer: 64 * 1024 * 1024 }); + // With a catalog or a library installed first, the tree was clean a moment ago and the only changes since are this + // script's own, checked above. + const update = positionals.filter((spec) => !firstNames.includes(spec.slice(0, spec.lastIndexOf('@')))); + const dirty = inlined || first.length ? ['--allow-dirty'] : []; + const args = yarn ? ['exec', '--yes', `--package=${cli[0]}`, '--', 'ng', 'update', ...update, ...dirty] : ['dlx', cli[0], 'update', ...update, ...dirty]; + const run = (argv) => spawnSync(runner.file, [...runner.prefix, ...argv], options); const result = run(args); let exitCode = result.status ?? 1; let catalog = null, install = null; @@ -205,6 +238,7 @@ function main() { if (values.json) { console.log(JSON.stringify({ exitCode, targetMajor: major, command: [yarn ? 'npm' : 'pnpm', ...args], stdout: redact(result.stdout), stderr: redact(result.stderr), + ...(first.length ? { installedFirst: first } : {}), ...(inlined ? { catalog, install: install && { exitCode: install.status, stdout: redact(install.stdout), stderr: redact(install.stderr) } } : {}), error: result.error ? 'The update process did not finish. Inspect its outcome before another attempt.' : null, }, null, 2)); diff --git a/test/helpers/harness.mjs b/test/helpers/harness.mjs index bad0821..cc02756 100644 --- a/test/helpers/harness.mjs +++ b/test/helpers/harness.mjs @@ -138,24 +138,33 @@ export function project({ deps = {}, devDeps = {}, lock = null, files = {}, nx = return { dir, remove: () => rmSync(dir, { recursive: true, force: true }) }; } -export function runScript(script, args, { cwd, env = {} } = {}) { +// node: the Node version the script sees itself running on, so a test can try one this machine doesn't have. +export function runScript(script, args, { cwd, env = {}, node } = {}) { + const preload = node ? ['--import', `data:text/javascript,Object.defineProperty(process,'version',{value:'v${node}'})`] : []; return new Promise((resolve) => { - execFile(process.execPath, [script, ...args], { cwd, env: { ...process.env, ...env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => { + execFile(process.execPath, [...preload, script, ...args], { cwd, env: { ...process.env, ...env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => { resolve({ code: err ? (typeof err.code === 'number' ? err.code : 1) : 0, stdout, stderr }); }); }); } -export async function plan(packages, proj, { to, next = false, text = false, registryOptions } = {}) { +// own: a company's packages on a registry of their own, { scope: '@acme', packages: [...] }, named in the project's +// .npmrc the way a team sets it up. rcDir puts that .npmrc in another folder, such as a pnpm workspace's root, and auth +// makes the registry want the token the .npmrc then holds. +export async function plan(packages, proj, { to, next = false, text = false, registryOptions, own = null, node } = {}) { const registry = await startRegistry(packages, registryOptions); + const company = own && await startRegistry(own.packages, { requireAuth: Boolean(own.auth) }); + const token = company && own.auth ? `${company.url.replace(/^http:/, '')}:_authToken=test-only\n` : ''; + if (company) writeFileSync(join(own.rcDir || proj.dir, '.npmrc'), `${own.scope}:registry=${company.url}\n${token}`); try { const args = ['--project', proj.dir]; if (to !== undefined) args.push('--to', String(to)); if (next) args.push('--next'); if (!text) args.push('--json'); - const out = await runScript(PLAN, args, { cwd: proj.dir, env: { npm_config_registry: registry.url } }); + const out = await runScript(PLAN, args, { cwd: proj.dir, env: { npm_config_registry: registry.url }, node }); return { ...out, plan: !text && out.stdout ? JSON.parse(out.stdout) : null }; } finally { await registry.close(); + if (company) await company.close(); } } diff --git a/test/plan.test.mjs b/test/plan.test.mjs index a25dae1..9e850f7 100644 --- a/test/plan.test.mjs +++ b/test/plan.test.mjs @@ -217,6 +217,91 @@ test('a deprecated library that npm still lets through stays unconfirmed, and th assert.match(text.stdout, /DEPRECATED on npm: "This package has been deprecated"/); }); +test('a blocker from a scope with a registry of its own may be the team\'s, so the plan gives the order to upgrade it in', async () => { + // Acme's libraries come from the company registry and stop at Angular 20, so the app is waiting on its own team. + // forms needs theme, so theme goes first, though forms comes first by name. + const theme = pkg('@acme/theme', { '20.4.0': { peer: '^20.0.0', at: '2025-09-01' } }); + const forms = pkg('@acme/forms', { '20.4.0': { peers: { '@angular/core': '^20.0.0', '@acme/theme': '^20.4.0' }, at: '2025-09-01' } }); + const options = { deps: { ...base, '@acme/forms': '^20.4.0', '@acme/theme': '^20.4.0' }, lock: { ...lockBase, '@acme/forms': '20.4.0', '@acme/theme': '20.4.0' } }; + const r = await plan(world(), app(options), { to: 21, own: { scope: '@acme', packages: [theme, forms] } }); + assert.equal(r.plan.status, 'blocked'); + assert.deepEqual(r.plan.ownBlockers, ['@acme/theme', '@acme/forms']); + assert.ok(r.plan.blockers.every((b) => /^http:\/\/127\.0\.0\.1:\d+\/$/.test(b.ownRegistry)), JSON.stringify(r.plan.blockers)); + + const text = (await plan(world(), app(options), { to: 21, text: true, own: { scope: '@acme', packages: [theme, forms] } })).stdout; + assert.match(text, /Note: @acme\/theme and @acme\/forms come from the registry your npm config sets for @acme\. If they are your team's, each can be upgraded first, with a release that accepts Angular 21, in this order: @acme\/theme, then @acme\/forms, which needs @acme\/theme\./); + assert.match(text, /@acme\/forms: no stable release .*; your npm config sets a registry for @acme, http:\/\/127\.0\.0\.1:\d+\/\./); + + // The same libraries on the default registry, as a company-wide mirror would serve them, say nothing about owners. + const mirrored = await plan(world(theme, forms), app(options), { to: 21 }); + assert.equal(mirrored.plan.status, 'blocked'); + assert.deepEqual(mirrored.plan.blockers.map((b) => b.name), ['@acme/forms', '@acme/theme']); + assert.equal(mirrored.plan.ownBlockers, undefined); + assert.ok(mirrored.plan.blockers.every((b) => !b.ownRegistry)); +}); + +test('a licensed vendor sets up its registry the same way, so the plan says only what the npm config shows', async () => { + // Infragistics serves its licensed Angular packages from a registry set for @infragistics, as a team serves its own. + const ignite = pkg('@infragistics/igniteui-angular', { '20.1.0': { peer: '^20.0.0', at: '2025-09-01' } }); + const deps = { '@infragistics/igniteui-angular': '20.1.0' }; + const r = await plan(world(), app({ deps: { ...base, ...deps }, lock: { ...lockBase, ...deps } }), { to: 21, own: { scope: '@infragistics', packages: [ignite] } }); + assert.match(r.plan.blockers[0].reason, /; your npm config sets a registry for @infragistics, http:\/\/127\.0\.0\.1:\d+\/$/); + // One library has no order to give. + assert.equal(r.plan.notes.at(-1), "@infragistics/igniteui-angular comes from the registry your npm config sets for @infragistics. If it is your team's, it can be upgraded first, with a release that accepts Angular 21."); +}); + +test('libraries that need each other are named as such, and one that only needs them comes after all of them', async () => { + // b needs x, and x and y need each other, so x and y go first, together, though b comes first by name. + const lib = (name, needs) => pkg(name, { '20.4.0': { peers: { '@angular/core': '^20.0.0', [needs]: '^20.4.0' }, at: '2025-09-01' } }); + const libs = [lib('@acme/b', '@acme/x'), lib('@acme/x', '@acme/y'), lib('@acme/y', '@acme/x')]; + const deps = Object.fromEntries(libs.map((l) => [l.name, '20.4.0'])); + const r = await plan(world(), app({ deps: { ...base, ...deps }, lock: { ...lockBase, ...deps } }), { to: 21, own: { scope: '@acme', packages: libs } }); + assert.deepEqual(r.plan.ownBlockers, ['@acme/x', '@acme/y', '@acme/b']); + assert.match(r.plan.notes.find((n) => n.includes('@acme/b')), /in this order: @acme\/x, which needs @acme\/y, then @acme\/y, which needs @acme\/x, then @acme\/b, which needs @acme\/x\. @acme\/x and @acme\/y need each other, so no order works for them: upgrade and publish them together\.$/); +}); + +test('a ring of three libraries that need each other is named whole, and apart from another ring', async () => { + // x needs y, y needs z and z needs x, so each one's release needs the other two's. b only needs x, so it's no part of + // the ring and comes after all of it, and c and d are a ring of their own. + const lib = (name, needs) => pkg(name, { '20.4.0': { peers: { '@angular/core': '^20.0.0', [needs]: '^20.4.0' }, at: '2025-09-01' } }); + const ring = [lib('@acme/x', '@acme/y'), lib('@acme/y', '@acme/z'), lib('@acme/z', '@acme/x')]; + for (const [libs, order, together] of [ + [ring, ['@acme/x', '@acme/z', '@acme/y'], '@acme/x, @acme/z and @acme/y'], + [[lib('@acme/b', '@acme/x'), ...ring], ['@acme/x', '@acme/z', '@acme/y', '@acme/b'], '@acme/x, @acme/z and @acme/y'], + [[lib('@acme/c', '@acme/d'), lib('@acme/d', '@acme/c'), ...ring], ['@acme/c', '@acme/d', '@acme/x', '@acme/z', '@acme/y'], '@acme/c and @acme/d, and @acme/x, @acme/z and @acme/y'], + ]) { + const deps = Object.fromEntries(libs.map((l) => [l.name, '20.4.0'])); + const r = await plan(world(), app({ deps: { ...base, ...deps }, lock: { ...lockBase, ...deps } }), { to: 21, own: { scope: '@acme', packages: libs } }); + assert.deepEqual(r.plan.ownBlockers, order); + assert.ok(r.plan.notes.at(-1).endsWith(`. ${together} need each other, so no order works for them: upgrade and publish them together.`), r.plan.notes.at(-1)); + } +}); + +test('an optional peer is no need, so it can\'t turn the order around', async () => { + // theme works with forms if it's there, and forms needs theme. + const theme = pkg('@acme/theme', { '20.4.0': { peers: { '@angular/core': '^20.0.0', '@acme/forms': '^20.4.0' }, at: '2025-09-01' } }); + theme.versions['20.4.0'].peerDependenciesMeta = { '@acme/forms': { optional: true } }; + const forms = pkg('@acme/forms', { '20.4.0': { peers: { '@angular/core': '^20.0.0', '@acme/theme': '^20.4.0' }, at: '2025-09-01' } }); + const deps = { '@acme/forms': '20.4.0', '@acme/theme': '20.4.0' }; + const r = await plan(world(), app({ deps: { ...base, ...deps }, lock: { ...lockBase, ...deps } }), { to: 21, own: { scope: '@acme', packages: [theme, forms] } }); + assert.deepEqual(r.plan.ownBlockers, ['@acme/theme', '@acme/forms']); +}); + +test('a scope pointed at the default registry, as a mirror is set up, says nothing about owners and still blocks', async () => { + const theme = pkg('@acme/theme', { '20.4.0': { peer: '^20.0.0', at: '2025-09-01' } }); + const registry = await startRegistry(world(theme)); + const p = app({ deps: { ...base, '@acme/theme': '^20.4.0' }, lock: { ...lockBase, '@acme/theme': '20.4.0' }, files: { '.npmrc': `@acme:registry=${registry.url}\n` } }); + try { + const r = await runScript(PLAN, ['--project', p.dir, '--to', '21', '--json'], { cwd: p.dir, env: { npm_config_registry: registry.url } }); + const result = JSON.parse(r.stdout); + assert.equal(result.status, 'blocked'); + assert.deepEqual(result.blockers.map((b) => b.name), ['@acme/theme']); + assert.equal(result.ownBlockers, undefined); + } finally { + await registry.close(); + } +}); + // ---- Things npm cannot answer --------------------------------------------------------------------------------------- test('a library with no Angular peer is reported as unchecked and does not block', async () => { @@ -581,6 +666,162 @@ test('pnpm bootstrap: the printed Run command selects the target CLI, not the lo assert.doesNotMatch(r.stdout, /Run: pnpm exec ng update/); }); +// A test project is a repo of its own, so the search for the repo's root stops there, whatever the folders above it hold. +const git = { '.git/HEAD': 'ref: refs/heads/main\n' }; +// Angular 22's real Node range, on the major a test aims at: 20 is below it, a bare 22 or 24 floats into it, and 24.0 is +// refused. +const A22 = '^22.22.3 || ^24.15.0 || >=26.0.0'; +const nodeWorld = (major = 21) => { + const [core, compiler] = angular(); + core.versions[`${major}.2.0`].engines = { node: A22 }; + return [core, compiler, typescript(), pkg('rxjs', { '7.8.2': {} })]; +}; +const setupNode = (version) => `jobs:\n test:\n steps:\n - uses: actions/setup-node@v4\n with:\n node-version: '${version}'\n`; +const pinsOf = async (files) => { + const r = await plan(nodeWorld(), app({ deps: base, lock: lockBase, files: { ...git, ...files } }), { to: 21 }); + assert.equal(r.code, 0, r.stderr); + return r.plan.hops[0].node.pins || []; +}; +const engines = (node) => ({ 'package.json': JSON.stringify({ name: 'app', dependencies: base, engines: { node } }) }); + +test('the repo\'s own Node pins below a major\'s range are named under it, from the dev container, a Dockerfile, .nvmrc and a CI workflow', async () => { + // The enterprise run's agent searched JSON and YAML for pins and missed the dev container's Dockerfile. + const files = { + ...git, + '.devcontainer/Dockerfile': 'FROM node:22.12.0-bookworm\nRUN corepack enable\n', + // A dev container image's tag can start with the image's own version, 5.2 here, or be only that, as 5-trixie is. + '.devcontainer/devcontainer.json': '{ "build": { "dockerfile": "Dockerfile" }, "image": "mcr.microsoft.com/devcontainers/typescript-node:5.2-24-bookworm" }', + Dockerfile: 'FROM mcr.microsoft.com/devcontainers/typescript-node:5-trixie\n', + '.nvmrc': '22\n', + '.github/workflows/ci.yml': setupNode('20'), + }; + const r = await plan(nodeWorld(), app({ deps: base, lock: lockBase, files }), { to: 21 }); + assert.deepEqual(r.plan.hops[0].node.pins, [{ file: '.devcontainer/Dockerfile', value: 'node:22.12.0-bookworm' }, { file: '.github/workflows/ci.yml', value: '20' }]); + const text = await plan(nodeWorld(), app({ deps: base, lock: lockBase, files }), { to: 21, text: true }); + assert.match(text.stdout, /Node pins below that, in the repo: \.devcontainer\/Dockerfile \(node:22\.12\.0-bookworm\), \.github\/workflows\/ci\.yml \(20\)/); +}); + +test('every place the plan reads a Node pin names it when it is below the range', async () => { + // Each source holds a value below the range, so a source the plan stopped reading would be missing here. + const files = { + ...git, + 'package.json': JSON.stringify({ name: 'app', dependencies: base, engines: { node: '>=20' }, volta: { node: '20.18.0' } }), + '.nvmrc': '20\n', + '.node-version': '20.11.0\n', + '.tool-versions': 'nodejs 20.18.0\n', + '.devcontainer/Dockerfile': 'FROM node:20-bookworm\n', + '.devcontainer/devcontainer.json': '{ "image": "mcr.microsoft.com/devcontainers/javascript-node:4.0.10-20-bookworm", "features": { "ghcr.io/devcontainers/features/node:1": { "version": "20" } } }', + '.github/workflows/ci.yml': setupNode('20'), + }; + const r = await plan(nodeWorld(), app({ lock: lockBase, files }), { to: 21 }); + assert.deepEqual(r.plan.hops[0].node.pins, [ + { file: '.nvmrc', value: '20' }, { file: '.node-version', value: '20.11.0' }, { file: '.tool-versions', value: '20.18.0' }, + { file: '.devcontainer/Dockerfile', value: 'node:20-bookworm' }, + { file: '.devcontainer/devcontainer.json', value: 'mcr.microsoft.com/devcontainers/javascript-node:4.0.10-20-bookworm' }, + { file: '.devcontainer/devcontainer.json', value: '20' }, + { file: 'package.json', value: '>=20' }, { file: 'package.json', value: '20.18.0' }, + { file: '.github/workflows/ci.yml', value: '20' }, + ]); +}); + +// A workflow whose setup-node reads a matrix key, as `node-version: ${{ matrix. }}`. +const matrixWorkflow = (lines, key) => `jobs:\n test:\n strategy:\n matrix:\n${lines} steps:\n - uses: actions/setup-node@v4\n with:\n node-version: \${{ matrix.${key} }}\n`; + +test('a Node pin written the way docker init, mise or a GitHub matrix writes it is read too', async () => { + assert.deepEqual(await pinsOf({ + // docker init's template: the version is an ARG's default, used in FROM. + Dockerfile: '# syntax=docker/dockerfile:1\nARG NODE_VERSION=20.11.0\nFROM node:${NODE_VERSION}-alpine as base\n', + // mise reads node as well as asdf's nodejs. + '.tool-versions': 'node 20.18.0\n', + // A matrix key of any name, in a flow list or one item a line, and with Windows line endings, as git can check out. + '.github/workflows/a.yml': matrixWorkflow(' node: [18, 22]\n', 'node').replace(/\n/g, '\r\n'), + '.github/workflows/b.yml': matrixWorkflow(' node-version:\n - 16.x\n - 22.x\n', 'node-version'), + // ng-hop's own CI: include entries, quoted, with a comment between them. + '.github/workflows/c.yml': matrixWorkflow(" os: [ubuntu-latest]\n node: ['22']\n include:\n - os: ubuntu-latest\n node: '20'\n # The oldest one.\n - os: ubuntu-latest\n node: '19'\n", 'node'), + // Outside the matrix, a key of the same name is another action's: this version is pnpm's. + '.github/workflows/d.yml': matrixWorkflow(' version: [17, 24]\n', 'version').replace(' steps:\n', ' steps:\n - uses: pnpm/action-setup@v4\n with:\n version: 9\n'), + }), [ + { file: '.tool-versions', value: '20.18.0' }, { file: 'Dockerfile', value: 'node:20.11.0-alpine' }, + { file: '.github/workflows/a.yml', value: '18' }, { file: '.github/workflows/b.yml', value: '16.x' }, + { file: '.github/workflows/c.yml', value: '20' }, { file: '.github/workflows/c.yml', value: '19' }, + { file: '.github/workflows/d.yml', value: '17' }, + ]); +}); + +test('the forms read before are still read, and node-version-file is no version', async () => { + assert.deepEqual(await pinsOf({ + Dockerfile: 'FROM --platform=linux/amd64 node:20-alpine AS build\nFROM nginx:1.27\n', + '.github/workflows/a.yml': matrixWorkflow(' node-version: [18.x, 22.x]\n', 'node-version'), + '.github/workflows/b.yml': `${setupNode('24').replace("node-version: '24'", "node-version-file: '.nvmrc'")} old:\n steps:\n - uses: actions/setup-node@v4\n with:\n node-version: "20" # the one before\n`, + }), [ + { file: 'Dockerfile', value: 'node:20-alpine' }, + { file: '.github/workflows/a.yml', value: '18.x' }, { file: '.github/workflows/b.yml', value: '20' }, + ]); +}); + +test('a list of Node versions one a line is read past a comment after an item or between items', async () => { + // 22.x fits, so only a list read to its end names 18.x. + assert.deepEqual(await pinsOf({ + '.github/workflows/ci.yml': matrixWorkflow(' node-version:\n - 22.x # current\n # - 21.x\n - 18.x\n', 'node-version'), + }), [{ file: '.github/workflows/ci.yml', value: '18.x' }]); +}); + +test('a commented-out line is not a pin, and doesn\'t hide the line in use', async () => { + // devcontainer.json allows comments, and so does a workflow's YAML, so either can keep an old pin as one. + assert.deepEqual(await pinsOf({ + '.devcontainer/devcontainer.json': '{\n // "image": "mcr.microsoft.com/devcontainers/typescript-node:24-bookworm",\n "image": "mcr.microsoft.com/devcontainers/typescript-node:20-bookworm"\n}\n', + '.github/workflows/ci.yml': `${setupNode('24')} # node-version: 16.x\n`, + }), [{ file: '.devcontainer/devcontainer.json', value: 'mcr.microsoft.com/devcontainers/typescript-node:20-bookworm' }]); +}); + +test('an engines range is below the range when the lowest Node it allows is one the range refuses', async () => { + // ">=24" allows Node 24.0, and Angular's CLI stops on 24.0 to 24.14 when the range starts 24 at 24.15. + assert.deepEqual(await pinsOf(engines('>=24')), [{ file: 'package.json', value: '>=24' }]); + assert.deepEqual(await pinsOf(engines('>=24.15.0')), []); +}); + +test('a pin semver can\'t read, a range nothing meets or a number past 2^53, doesn\'t stop the plan', async () => { + assert.deepEqual(await pinsOf({ ...engines('>=22 <20'), '.nvmrc': '99999999999999999\n' }), []); +}); + +test('an engines range that allows any Node pins none, while one that stops below the range is still named', async () => { + // "*" keeps the team on no Node in particular, and it can't be raised and keep its form. "<20" starts at 0.0.0 too. + assert.deepEqual(await pinsOf(engines('*')), []); + assert.deepEqual(await pinsOf(engines('<20')), [{ file: 'package.json', value: '<20' }]); +}); + +test('an app already on the target still hears about a Node pin below its range', async () => { + const deps = { ...base, '@angular/core': '^22.0.0', '@angular/cli': '^22.0.0' }; + const options = { deps, lock: { ...lockBase, '@angular/core': '22.2.0' }, files: { ...git, '.devcontainer/Dockerfile': 'FROM node:22.12.0-bookworm\n' } }; + const r = await plan(nodeWorld(22), app(options), { to: 22 }); + assert.equal(r.plan.status, 'already-there'); + assert.deepEqual(r.plan.nodePins, { range: A22, pins: [{ file: '.devcontainer/Dockerfile', value: 'node:22.12.0-bookworm' }] }); + const text = await plan(nodeWorld(22), app(options), { to: 22, text: true }); + assert.ok(text.stdout.includes(`\n Node pins below what Angular 22 needs (${A22}), in the repo: .devcontainer/Dockerfile (node:22.12.0-bookworm)\n`), text.stdout); + // A dev container on a Node inside the range has nothing to raise. + const fits = await plan(nodeWorld(22), app({ ...options, files: { ...git, '.devcontainer/Dockerfile': 'FROM node:24-bookworm\n' } }), { to: 22 }); + assert.equal(fits.plan.status, 'already-there'); + assert.equal(fits.plan.nodePins, undefined); +}); + +test('an app below the repo root gets the pins kept at the root too', async () => { + // A workspace keeps engines and volta in the root's package.json, and Volta's members extend it. + const lock = JSON.stringify({ lockfileVersion: 3, packages: Object.fromEntries(Object.entries(lockBase).map(([n, version]) => [`node_modules/${n}`, { version }])) }); + const repo = app({ files: { + ...git, + 'package.json': JSON.stringify({ name: 'mono', private: true, engines: { node: '>=20' }, volta: { node: '20.18.0' } }), + 'apps/web/package.json': JSON.stringify({ name: 'web', dependencies: base, volta: { extends: '../../package.json' } }), + 'apps/web/package-lock.json': lock, + '.github/workflows/ci.yml': setupNode('20'), + } }); + const r = await plan(nodeWorld(), { dir: join(repo.dir, 'apps', 'web') }, { to: 21 }); + assert.equal(r.code, 0, r.stderr); + assert.deepEqual(r.plan.hops[0].node.pins, [ + { file: '../../package.json', value: '>=20' }, { file: '../../package.json', value: '20.18.0' }, + { file: '../../.github/workflows/ci.yml', value: '20' }, + ]); +}); + test('Node outside a major\'s range is reported on that major', async () => { const [core, compiler] = angular(); core.versions['21.2.0'].engines = { node: '>=999.0.0' }; @@ -590,6 +831,13 @@ test('Node outside a major\'s range is reported on that major', async () => { assert.equal(r.plan.hops.find((h) => h.major === 22).node.status, 'unsupported'); }); +test('a Node in a gap of a major\'s range is too old, since a newer one fits and ng refuses this one', async () => { + // 24.14 is above 22.22 but below 24.15, in a gap of Angular 22's range, and its CLI won't start on it. + const r = await plan(nodeWorld(), app({ deps: base, lock: lockBase }), { to: 21, node: '24.14.1' }); + assert.equal(r.plan.project.node, '24.14.1'); + assert.equal(r.plan.hops[0].node.status, 'too-old'); +}); + // ---- Project state and refusing to guess ------------------------------------------------------------------------------ test('the lockfile wins over the declared range, so a resumed upgrade starts where it stopped', async () => { @@ -815,6 +1063,58 @@ test('pnpm: an app inside a workspace gets pnpm commands and its own entry in th assert.ok(r.plan.hops[0].command.startsWith(`pnpm exec node ${quotePath(UPDATE)}`)); }); +// An app in a pnpm workspace that uses its team's @acme/ui-core, which stops at Angular 20. +const uiCore = () => pkg('@acme/ui-core', { '20.4.0': { peer: '^20.0.0', at: '2025-09-01' } }); +const acmeWorkspace = ({ workspaces, files = {} } = {}) => { + const p = app({ files: { + 'package.json': JSON.stringify({ name: 'mono', private: true, ...(workspaces ? { workspaces } : {}) }), + 'pnpm-workspace.yaml': "packages:\n - 'apps/*'\n", + 'pnpm-lock.yaml': pnpmLock(' .: {}\n', pnpmImporter('apps/web')), + 'apps/web/package.json': JSON.stringify({ name: 'web', dependencies: { ...base, '@acme/ui-core': '^20.4.0' } }), + ...files, + } }); + return { root: p.dir, dir: join(p.dir, 'apps', 'web') }; +}; +const blockedOnUiCore = (r) => { + assert.equal(r.code, 2, r.stderr); + assert.deepEqual(r.plan.ownBlockers, ['@acme/ui-core']); +}; + +test('pnpm: the workspace root\'s .npmrc gives the team\'s registry and its token, as pnpm reads them there', async () => { + // npm reads only the .npmrc beside the app, so the plan asked the default registry for @acme/ui-core, found nothing, + // and called the app reachable. + const w = acmeWorkspace(); + blockedOnUiCore(await plan(world(), w, { to: 21, own: { scope: '@acme', packages: [uiCore()], rcDir: w.root, auth: true } })); +}); + +test('pnpm: a root that also declares npm workspaces doesn\'t stop the plan', async () => { + // npm refuses `config` below a root that declares workspaces, with ENOWORKSPACES. + const w = acmeWorkspace({ workspaces: ['apps/*'] }); + blockedOnUiCore(await plan(world(), w, { to: 21, own: { scope: '@acme', packages: [uiCore()], rcDir: w.root } })); +}); + +test('pnpm: the app\'s own .npmrc counts too, token included, as pnpm 10 and older lay it over the root\'s', async () => { + const w = acmeWorkspace({ workspaces: ['apps/*'] }); + blockedOnUiCore(await plan(world(), w, { to: 21, own: { scope: '@acme', packages: [uiCore()], auth: true } })); +}); + +test('pnpm: only the keys the app\'s own .npmrc sets go over the root\'s, so a mirror set at the root stays', async () => { + // npm's listing for the app's folder holds every default, the registry included, so laying all of it over the root's + // would undo the root's mirror. The user config makes that default a registry nothing answers on. + const [mirror, company] = await Promise.all([startRegistry(world()), startRegistry([uiCore()])]); + const w = acmeWorkspace({ files: { + '.npmrc': `registry=${mirror.url}\n@acme:registry=${company.url}\n`, + 'apps/web/.npmrc': 'save-exact=true\n', + 'user.npmrc': 'registry=http://127.0.0.1:9/\n', + } }); + try { + const r = await runScript(PLAN, ['--project', w.dir, '--to', '21', '--json'], { cwd: w.dir, env: { npm_config_userconfig: join(w.root, 'user.npmrc') } }); + blockedOnUiCore({ ...r, plan: r.stdout ? JSON.parse(r.stdout) : null }); + } finally { + await Promise.all([mirror.close(), company.close()]); + } +}); + test('pnpm: a lockfile in a parent folder with no workspace file has nothing to do with the app', async () => { const p = app({ deps: {}, files: { 'pnpm-lock.yaml': pnpmLock(pnpmImporter('apps/web')), 'apps/web/package.json': JSON.stringify({ name: 'web', dependencies: { '@angular/core': '^20.0.0' } }) } }); const r = await plan(world(), { dir: join(p.dir, 'apps', 'web') }, { to: 21 }); diff --git a/test/skill.test.mjs b/test/skill.test.mjs index 60a9ebd..14cfe8a 100644 --- a/test/skill.test.mjs +++ b/test/skill.test.mjs @@ -110,6 +110,167 @@ test('the skill only aims at a prerelease when someone asks to try one', () => { assert.match(readFileSync(join(SKILL_DIR, 'scripts', 'plan.mjs'), 'utf8'), /--next/); }); +// The section of troubleshooting.md that holds this text, which is also the positive control that it's still there. +function troubleSection(text) { + const trouble = readFileSync(join(SKILL_DIR, 'references', 'troubleshooting.md'), 'utf8'); + const at = trouble.indexOf(text); + assert.ok(at >= 0, `troubleshooting.md has to keep ${text}`); + const end = trouble.indexOf('\n## ', at); + return trouble.slice(trouble.lastIndexOf('\n## ', at), end < 0 ? undefined : end); +} +const youngRelease = 'Package "@acme/ui-core" has an incompatible peer dependency to "@angular/common" (requires "^21.2.0", would install "22.2.5")'; + +test('a release too young for ng update is blamed on its own filter, with a check that works', () => { + // pnpm 12.16.1 let a release ten minutes old in when asked by name, and wrote the exemption itself. Angular CLI 22.1's + // resolver is what skipped it, and `pnpm config get minimumReleaseAge` printed undefined for the one-day default. + const section = troubleSection(youngRelease); + assert.doesNotMatch(section, /won't resolve a version younger/); + assert.match(section, /prints `undefined`/); + assert.match(section, /npm view time/); +}); + +test('a release too young for ng update is covered on yarn 4.15 and newer too', () => { + // yarn 4.10.1 added npmMinimalAgeGate at 0, and 4.15.0 made it a day by default. 4.18.1 printed 1440 for it and + // refused a release itself until .yarnrc.yml preapproved it. + const section = troubleSection(youngRelease); + assert.match(section, /npmPreapprovedPackages/); + assert.match(section, /YN0016/); + const pointer = readFileSync(join(SKILL_DIR, 'references', 'troubleshooting.md'), 'utf8').split('\n').find((line) => line.includes("still quotes the old release's peers")); + assert.ok(pointer, 'the peer-error section has to point here'); + assert.match(pointer, /yarn 4\.15 or newer/); + assert.match(readFileSync(join(SKILL_DIR, 'references', 'libraries.md'), 'utf8'), /pnpm 11 or newer, or yarn 4\.15 or newer/); +}); + +test('a release too young for ng update goes in first through update.mjs, never with --allow-dirty by hand', () => { + // update.mjs rejects --allow-dirty and refuses a dirty tree, so the old step couldn't run, and installing a library + // first skips its migrations. + const section = troubleSection(youngRelease); + assert.match(section, /--install-first/); + assert.match(section, /ships migrations/); + assert.doesNotMatch(section, /with `--allow-dirty`/); + assert.match(readFileSync(join(SKILL_DIR, 'scripts', 'update.mjs'), 'utf8'), /'install-first'/); +}); + +test('a release-age exemption waits for the person, like an override', () => { + // It turns off a guard against a compromised release, and pnpm writes one without asking. + const rule = skill.split('\n').find((line) => line.includes('npmPreapprovedPackages') && line.includes("without the person's approval")); + assert.ok(rule, 'the scope list has to name release-age exemptions'); + assert.match(rule, /minimumReleaseAgeExclude/); +}); + +test('putting the tree back reaches a pnpm workspace root above the app', () => { + // pnpm writes an app's lockfile and exemptions at the workspace root, and git 2.55's `git restore .` run in the app's + // folder left them changed, so the next update.mjs refused a tree that wasn't clean. + const docs = [skill, ...readdirSync(join(SKILL_DIR, 'references')).map((f) => readFileSync(join(SKILL_DIR, 'references', f), 'utf8'))]; + assert.ok(docs.some((d) => d.includes('`git restore :/`')), 'positive control: the docs still say how to put the tree back'); + for (const doc of docs) assert.doesNotMatch(doc, /git restore \.(?:[\s`]|$)/m); +}); + +test('a scope registry ng update cannot see goes where it reads, never with a literal token', () => { + // Angular CLI 21.2.29, run in an app below a pnpm workspace root, asked the public registry for the team's package: + // up to 22.0, ng update reads only the app folder's .npmrc and the user's and global ones. + const section = troubleSection('404 Not Found - GET https://registry.npmjs.org/@acme%2fui-core'); + assert.match(section, /Up to Angular CLI 22\.0/); + assert.match(section, /`~\/\.npmrc`/); + assert.match(section, /Never copy a token itself into a file git tracks/); +}); + +// One lettered step of the upgrade loop in SKILL.md, from its bold label to the next one. +function step(letter) { + const start = skill.indexOf(`**${letter}. `); + assert.ok(start >= 0, `SKILL.md has a step ${letter}`); + const next = skill.indexOf(`**${String.fromCharCode(letter.charCodeAt(0) + 1)}. `, start); + return skill.slice(start, next < 0 ? undefined : next); +} + +test('installing what ng update left behind watches for an exemption pnpm writes on its own', () => { + // pnpm 12.16.1's `pnpm add` of a release under a day old wrote one into pnpm-workspace.yaml without asking, and + // taking out only that entry left the release in the lockfile. + assert.match(step('e'), /Install anything the plan/, 'positive control: this is the step that installs'); + assert.match(step('e'), /minimumReleaseAgeExclude/); +}); + +test('the skill raises the Node pins the plan names when the app is already on the target', () => { + // On Angular 22 with a dev container still on node:22.12.0, the plan named the pin under "Nothing to upgrade", and the + // skill's already-there path ended with "That's the whole job". + const label = skill.split('\n').find((line) => line.includes('Node pins below what Angular')); + assert.ok(label, 'the skill has to answer the line plan.mjs prints'); + assert.match(readFileSync(join(SKILL_DIR, 'scripts', 'plan.mjs'), 'utf8'), /Node pins below what Angular/); + const already = skill.split('\n').find((line) => line.includes("If it's already on the target major")); + assert.ok(already, 'positive control: step 2 has an already-there path'); + assert.match(already, /Node pins/); +}); + +test('the skill looks for Node pins past the files the plan reads', () => { + // The plan reads a fixed set of files, and passes over a version it can't read, such as lts/iron in .nvmrc. An agent + // that took its list as complete would repeat the enterprise run's missed dev container. + assert.match(step('e'), /Raise each Node pin the plan lists/, 'positive control: this is the step that raises pins'); + assert.match(step('e'), /search the repo for other pins/); + assert.match(step('e'), /where you looked/); +}); + +test('a Node pin outside the app\'s folder is raised only when it builds, tests or runs this app', () => { + // The plan also reads the repo root's package.json, Dockerfile and workflows, which in a monorepo can belong to another + // service, and this app's checks can't catch what raising its Node does there. + assert.match(step('e'), /Raise each Node pin the plan lists/, 'positive control: this is the step that raises pins'); + assert.match(step('e'), /starts with `\.\.\/`/); + assert.match(step('e'), /builds, tests or runs this app/); +}); + +test('a Node in a gap of Angular\'s range is too old, and a newer release of the same line can fix it', () => { + // Angular 22 needs ^22.22.3 || ^24.15.0 || >=26.0.0. CLI 22.2.6 exits on Node 24.14.1, where 24.15 is enough. + assert.match(readFileSync(join(SKILL_DIR, 'scripts', 'plan.mjs'), 'utf8'), /'too-old'/, 'positive control: the plan prints too-old'); + const rule = skill.split('\n').find((line) => line.includes('`Node: too-old`')); + assert.ok(rule, 'the skill has to answer Node: too-old'); + assert.match(rule, /refuse to run/); + // CLI 22.2.6 only warns on Node 23 and 25, which sit in gaps of the same range, so the plan calls them too-old too. + assert.match(rule, /odd majors/); + assert.match(rule, /newer release of the line they're on/); + assert.match(rule, /`unsupported` means Node is newer than every/); +}); + +const libraries = () => readFileSync(join(SKILL_DIR, 'references', 'libraries.md'), 'utf8'); + +test('a workspace of several libraries builds with its own scripts, not plain ng build', () => { + // Angular CLI 22.2.6's ng build at the root of acme-ui stopped with this, and step 3 says to stop when the build fails. + assert.match(libraries(), /Cannot determine project for command/); +}); + +test('the publish question says the code is unpushed, and where a dry run says it would go', () => { + // A dry run of pnpm 12.16.1 sent a library to its publishConfig registry over the workspace's .npmrc, npm 11.19.0 the + // other way round, and run from inside dist both named the public registry. + const publish = libraries().slice(libraries().indexOf('## Publishing waits for the person')); + assert.match(publish, /stop before it/, 'positive control: this is the publish step'); + assert.match(publish, /--dry-run/); + assert.match(publish, /pushed/); +}); + +test('a blocker whose scope has a registry of its own is confirmed as the team\'s before upgrading it first', () => { + // Infragistics and Font Awesome Pro have customers give their scope a registry of its own, which the plan flags the + // same way, and for a vendor's maintained library waiting is the usual advice. + const blocked = readFileSync(join(SKILL_DIR, 'references', 'blocked.md'), 'utf8'); + const own = blocked.split('\n').find((line) => line.startsWith("- **Whether it's the person's own.**")); + assert.ok(own, 'positive control: blocked.md asks whether a blocker is theirs'); + assert.match(own, /vendor/); + assert.match(own, /npm view repository\.url author/); + assert.doesNotMatch(skill, /when the plan says it comes from their own registry/); +}); + +test('the team\'s libraries go only to the major the app needs next, since the app moves one major at a time', () => { + // On the fake registry, an app on Angular 20 whose library had a release for 22 and none for 21 was still blocked at 21. + assert.match(libraries(), /--to /); + assert.match(readFileSync(join(SKILL_DIR, 'references', 'blocked.md'), 'utf8'), /to the Angular major the plan names/); +}); + +test('the team\'s libraries that need each other are upgraded and published together, as the plan says', () => { + // A cycle has no order to follow, and the plan names every library in it. + assert.match(readFileSync(join(SKILL_DIR, 'scripts', 'plan.mjs'), 'utf8'), /need each other, so no order works for them/, 'positive control: the plan says so'); + const first = readFileSync(join(SKILL_DIR, 'references', 'blocked.md'), 'utf8').split('\n').find((line) => line.startsWith('**Upgrade it first.**')); + assert.ok(first, 'positive control: blocked.md offers to upgrade the team\'s libraries first'); + assert.match(first, /need each other/); + assert.match(first, /upgrade and publish them together/); +}); + test('the marketplace points at the plugin', () => { const market = JSON.parse(readFileSync(join(ROOT, '.claude-plugin', 'marketplace.json'), 'utf8')); assert.ok(existsSync(join(ROOT, market.plugins[0].source, '.claude-plugin', 'plugin.json'))); diff --git a/test/update.test.mjs b/test/update.test.mjs index cacb873..8864733 100644 --- a/test/update.test.mjs +++ b/test/update.test.mjs @@ -185,6 +185,143 @@ test('pnpm updater: executable shims are resolved without running a command shel assert.equal(f.read().dispatch, 'true'); }); +// ---- installing a library first ------------------------------------------------------------------------------------ + +// Stands in for pnpm, yarn and npm, and logs each call. `add` saves each spec in package.json, in the section it's +// already in, and changes the lockfile, as pnpm 12 and yarn 4 both do. +const addDriver = `const fs = require('node:fs'); +const log = fs.existsSync(process.env.TEST_LOG) ? JSON.parse(fs.readFileSync(process.env.TEST_LOG, 'utf8')) : []; +log.push(process.argv.slice(2)); +fs.writeFileSync(process.env.TEST_LOG, JSON.stringify(log)); +if (process.argv[2] === 'add') { + if (process.env.TEST_ADD_FAILS) { console.error('ERR_PNPM_NO_MATURE_MATCHING_VERSION'); process.exit(1); } + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + for (const spec of process.argv.slice(3).filter((arg) => !arg.startsWith('-'))) { + const at = spec.lastIndexOf('@'), name = spec.slice(0, at); + (pkg.devDependencies?.[name] ? pkg.devDependencies : pkg.dependencies)[name] = spec.slice(at + 1); + } + fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\\n'); + fs.appendFileSync(fs.existsSync('yarn.lock') ? 'yarn.lock' : 'pnpm-lock.yaml', '# changed\\n'); + // pnpm 12 writes an exemption itself, creating the file if it has to, for a release the list doesn't cover. + if (process.env.TEST_ADD_EXEMPTS) fs.writeFileSync('pnpm-workspace.yaml', "minimumReleaseAgeExclude:\\n - '@acme/ui-core@22.0.0'\\n"); +} +`; + +function firstFixture(manifest, { yarn = false, workspace } = {}) { + const root = mkdtempSync(join(tmpdir(), 'ng-hop install-first-')); made.push(root); + const project = join(root, 'app'), bin = join(root, 'bin'); mkdirSync(project); mkdirSync(bin); + const log = join(root, 'log.json'); + writeFileSync(join(project, 'package.json'), `${JSON.stringify({ private: true, ...manifest }, null, 2)}\n`); + writeFileSync(join(project, yarn ? 'yarn.lock' : 'pnpm-lock.yaml'), '# lockfile\n'); + if (workspace) writeFileSync(join(project, 'pnpm-workspace.yaml'), workspace); + writeFileSync(join(project, 'tracked.txt'), 'clean\n'); + const git = (args) => { const r = spawnSync('git', args, { cwd: project, encoding: 'utf8' }); assert.equal(r.status, 0, r.stderr); }; + git(['init', '--initial-branch=main']); git(['add', '--all']); + git(['-c', 'user.name=Test fixture', '-c', 'user.email=fixture@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-m', 'Fixture']); + // pnpm arrives through npm_execpath, as `pnpm exec` sets it. A yarn project's update runs through npm, and its + // install through the yarn on the path, which on Windows is a .cmd shim. + const driver = join(root, yarn ? 'npm-cli.js' : 'pnpm.cjs'); + writeFileSync(driver, addDriver); + if (yarn && process.platform === 'win32') { + writeFileSync(join(bin, 'yarn.cjs'), addDriver); + writeFileSync(join(bin, 'yarn.cmd'), '@echo off\r\nnode "%~dp0\\yarn.cjs" %*\r\n'); + } else if (yarn) writeFileSync(join(bin, 'yarn'), '#!' + process.execPath + '\n' + addDriver, { mode: 0o755 }); + const pathKey = Object.keys(process.env).find(k => k.toLowerCase() === 'path') || 'PATH'; + return { + project, + env: { npm_execpath: driver, TEST_LOG: log, COREPACK_ROOT: '', [pathKey]: bin + delimiter + process.env[pathKey] }, + log: () => (existsSync(log) ? JSON.parse(readFileSync(log, 'utf8')) : []), + read: (file) => readFileSync(join(project, file), 'utf8'), + }; +} + +const acme = { dependencies: { '@angular/core': '21.2.0', '@acme/ui-core': '^21.4.0' }, devDependencies: { '@acme/ui-testing': '21.4.0' } }; +const acmeHop = ['@angular/core@22', '@angular/cli@22', '@acme/ui-core@22.0.0', '@acme/ui-testing@22.0.0']; +const first = ['--install-first', '@acme/ui-core', '--install-first', '@acme/ui-testing']; + +test('pnpm updater: --install-first installs those libraries first, in the form package.json uses, then updates the rest', async () => { + // Angular CLI 22.1 and newer skip a release younger than pnpm's minimum release age, a day by default, and read no + // exemption list. An installed release is read from node_modules instead, so installing it first gets it through. + const f = firstFixture(acme); + const r = await runScript(UPDATE, ['--json', ...first, ...acmeHop], { cwd: f.project, env: f.env }); + assert.equal(r.code, 0, r.stderr || r.stdout); + assert.deepEqual(f.log(), [ + ['add', '@acme/ui-core@^22.0.0', '@acme/ui-testing@22.0.0'], + ['dlx', '@angular/cli@22', 'update', '@angular/core@22', '@angular/cli@22', '--allow-dirty'], + ], 'the caret stays, an exact pin stays exact, and the update leaves out what is installed already'); + assert.deepEqual(JSON.parse(r.stdout).installedFirst, ['@acme/ui-core@^22.0.0', '@acme/ui-testing@22.0.0']); +}); + +test('pnpm updater: --install-first adds to the app with -w when the app is a workspace root that lists packages', async () => { + // pnpm 12.16.1 stopped with ERR_PNPM_ADDING_TO_ROOT in a root that listed two package patterns, until -w said the root + // was meant. It took the add as it was in a root with one pattern or with settings only. + for (const [workspace, flag, why] of [ + ["packages:\n - 'projects/*'\n - 'libs/*'\n", ['-w'], 'a root that lists packages'], + ['minimumReleaseAge: 1440\n', [], 'a root with settings only'], + ]) { + const f = firstFixture(acme, { workspace }); + const r = await runScript(UPDATE, ['--json', ...first, ...acmeHop], { cwd: f.project, env: f.env }); + assert.equal(r.code, 0, r.stderr || r.stdout); + assert.deepEqual(f.log()[0], ['add', ...flag, '@acme/ui-core@^22.0.0', '@acme/ui-testing@22.0.0'], why); + } +}); + +test('yarn updater: --install-first installs through the project\'s yarn, and the update still runs through npm', async () => { + const f = firstFixture(acme, { yarn: true }); + const r = await runScript(UPDATE, ['--json', ...first, ...acmeHop], { cwd: f.project, env: f.env }); + assert.equal(r.code, 0, r.stderr || r.stdout); + assert.deepEqual(f.log(), [ + ['add', '@acme/ui-core@^22.0.0', '@acme/ui-testing@22.0.0'], + ['exec', '--yes', '--package=@angular/cli@22', '--', 'ng', 'update', '@angular/core@22', '@angular/cli@22', '--allow-dirty'], + ]); +}); + +test('pnpm updater: --install-first still starts from a clean tree, with the working run above as its control', async () => { + // Leftovers from an interrupted major dirty the same files, and ng update would skip their migrations. + const f = firstFixture(acme); + writeFileSync(join(f.project, 'package.json'), f.read('package.json').replace('^21.4.0', '^22.0.0')); + const r = await runScript(UPDATE, ['--json', ...first, ...acmeHop], { cwd: f.project, env: f.env }); + assert.equal(r.code, 1); + assert.match(JSON.parse(r.stdout).error, /Repository is not clean/); + assert.deepEqual(f.log(), []); +}); + +test('pnpm updater: an install that changes more than package.json and the lockfile stops before the update', async () => { + // An exemption pnpm writes itself is a safeguard relaxed, which is the person's call, so it isn't carried into a hop. + const f = firstFixture(acme); + const r = await runScript(UPDATE, ['--json', ...first, ...acmeHop], { cwd: f.project, env: { ...f.env, TEST_ADD_EXEMPTS: '1' } }); + assert.equal(r.code, 1); + const { error } = JSON.parse(r.stdout); + assert.match(error, /pnpm-workspace\.yaml/); + assert.match(error, /no update was run/); + assert.deepEqual(f.log().map((call) => call[0]), ['add']); +}); + +test('pnpm updater: a failed install stops before the update', async () => { + const f = firstFixture(acme); + const r = await runScript(UPDATE, ['--json', ...first, ...acmeHop], { cwd: f.project, env: { ...f.env, TEST_ADD_FAILS: '1' } }); + assert.equal(r.code, 1); + assert.match(JSON.parse(r.stdout).error, /no update was run/); + assert.deepEqual(f.log().map((call) => call[0]), ['add']); +}); + +test('pnpm updater: --install-first takes only a library the command names at an exact version, with a range in package.json', async () => { + const cases = [ + [['--install-first', '@angular/core'], acmeHop, acme, 'Angular itself, whose migrations ng update has to run'], + [['--install-first', '@acme/ui-grid'], acmeHop, acme, 'a library the command does not name'], + [['--install-first', '@acme/ui-core'], ['@angular/core@22', '@angular/cli@22', '@acme/ui-core@22'], acme, 'a major, not the exact version the plan names'], + [['--install-first', '@acme/ui-core'], acmeHop, { dependencies: { '@angular/core': '21.2.0', '@acme/ui-core': 'catalog:' } }, 'a catalog entry, which an add would turn into a plain version'], + [['--install-first', '@acme/ui-testing'], acmeHop, { dependencies: { '@angular/core': '21.2.0' } }, 'a library package.json does not list'], + ]; + for (const [option, hop, manifest, why] of cases) { + const f = firstFixture(manifest); + const r = await runScript(UPDATE, ['--json', ...option, ...hop], { cwd: f.project, env: f.env }); + assert.equal(r.code, 1, why); + assert.match(JSON.parse(r.stdout).error, /--install-first takes/, why); + assert.deepEqual(f.log(), [], why); + } +}); + // ---- pnpm catalogs ------------------------------------------------------------------------------------------------ // Stands in for pnpm. On `dlx … update` it plays ng update, which moves whatever versions it finds in package.json.