diff --git a/.github/actions/deploy-docs-site/action.yml b/.github/actions/deploy-docs-site/action.yml index 91e661d0476d..e29d2b2aeeb9 100644 --- a/.github/actions/deploy-docs-site/action.yml +++ b/.github/actions/deploy-docs-site/action.yml @@ -5,6 +5,9 @@ inputs: serviceKey: description: 'The service key used for firebase deployments.' required: true + githubReleaseTrainReadToken: + description: 'GitHub access token for reading release trains without rate limits.' + required: true configPath: description: 'The path to the firebase config file.' required: true diff --git a/.github/actions/deploy-docs-site/lib/credential.ts b/.github/actions/deploy-docs-site/lib/credential.ts index 69535c7cc0a6..177f4cd0a97c 100644 --- a/.github/actions/deploy-docs-site/lib/credential.ts +++ b/.github/actions/deploy-docs-site/lib/credential.ts @@ -13,3 +13,8 @@ export function getCredentialFilePath(): string { } return credentialFilePath; } + +/** Github access token. Used for querying the active release trains. */ +export const githubReleaseTrainReadToken: string = getInput('githubReleaseTrainReadToken', { + required: true, +}); diff --git a/.github/actions/deploy-docs-site/lib/deployments.ts b/.github/actions/deploy-docs-site/lib/deployments.ts index 0686800a72b3..2690ca34726a 100644 --- a/.github/actions/deploy-docs-site/lib/deployments.ts +++ b/.github/actions/deploy-docs-site/lib/deployments.ts @@ -1,6 +1,6 @@ import {fetchLongTermSupportBranchesFromNpm, ActiveReleaseTrains} from '@angular/ng-dev'; import {ReleaseConfig} from '@angular/ng-dev'; -import {GitClient} from '@angular/ng-dev'; +import {AuthenticatedGitClient} from '@angular/ng-dev'; export interface Deployment { branch: string; @@ -14,7 +14,7 @@ export interface Deployment { export type Deployments = Map; export async function getDeployments(): Promise { - const {github} = await GitClient.get(); + const {github} = await AuthenticatedGitClient.get(); const releaseTrains = await ActiveReleaseTrains.fetch({ api: github, name: 'angular', diff --git a/.github/actions/deploy-docs-site/lib/main.ts b/.github/actions/deploy-docs-site/lib/main.ts index 498fa7ed227a..250cbe05ea2d 100644 --- a/.github/actions/deploy-docs-site/lib/main.ts +++ b/.github/actions/deploy-docs-site/lib/main.ts @@ -2,7 +2,8 @@ import {getInput, setFailed} from '@actions/core'; import {context} from '@actions/github'; import {deployToFirebase, setupRedirect} from './deploy'; import {getDeployments} from './deployments'; -import {GithubConfig, setConfig} from '@angular/ng-dev'; +import {AuthenticatedGitClient, GithubConfig, setConfig} from '@angular/ng-dev'; +import {githubReleaseTrainReadToken} from './credential'; const refMatcher = /refs\/heads\/(.*)/; @@ -14,6 +15,9 @@ async function deployDocs() { owner: 'angular', }, }); + + AuthenticatedGitClient.configure(githubReleaseTrainReadToken); + if (context.eventName !== 'push') { throw Error(); } diff --git a/.github/actions/deploy-docs-site/main.js b/.github/actions/deploy-docs-site/main.js index acc33ef171b8..888eba7aee5c 100644 --- a/.github/actions/deploy-docs-site/main.js +++ b/.github/actions/deploy-docs-site/main.js @@ -7433,6 +7433,7 @@ var require_lrucache = __commonJS({ // var require_range = __commonJS({ ""(exports, module) { + var SPACE_CHARACTERS = /\s+/g; var Range = class { constructor(range, options) { options = parseOptions(options); @@ -7446,13 +7447,13 @@ var require_range = __commonJS({ if (range instanceof Comparator) { this.raw = range.value; this.set = [[range]]; - this.format(); + this.formatted = void 0; return this; } this.options = options; this.loose = !!options.loose; this.includePrerelease = !!options.includePrerelease; - this.raw = range.trim().split(/\s+/).join(" "); + this.raw = range.trim().replace(SPACE_CHARACTERS, " "); this.set = this.raw.split("||").map((r) => this.parseRange(r.trim())).filter((c) => c.length); if (!this.set.length) { throw new TypeError(`Invalid SemVer Range: ${this.raw}`); @@ -7471,10 +7472,27 @@ var require_range = __commonJS({ } } } - this.format(); + this.formatted = void 0; + } + get range() { + if (this.formatted === void 0) { + this.formatted = ""; + for (let i = 0; i < this.set.length; i++) { + if (i > 0) { + this.formatted += "||"; + } + const comps = this.set[i]; + for (let k = 0; k < comps.length; k++) { + if (k > 0) { + this.formatted += " "; + } + this.formatted += comps[k].toString().trim(); + } + } + } + return this.formatted; } format() { - this.range = this.set.map((comps) => comps.join(" ").trim()).join("||").trim(); return this.range; } toString() { @@ -11344,6 +11362,9 @@ function getCredentialFilePath() { } return credentialFilePath; } +var githubReleaseTrainReadToken = (0, import_core.getInput)("githubReleaseTrainReadToken", { + required: true +}); // async function deployToFirebase(deployment, configPath, distDirPath) { @@ -12977,7 +12998,7 @@ AuthenticatedGitClient._authenticatedInstance = null; // async function getDeployments() { - const { github } = await GitClient.get(); + const { github } = await AuthenticatedGitClient.get(); const releaseTrains = await ActiveReleaseTrains.fetch({ api: github, name: "angular", @@ -13033,6 +13054,7 @@ async function deployDocs() { owner: "angular" } }); + AuthenticatedGitClient.configure(githubReleaseTrainReadToken); if (import_github3.context.eventName !== "push") { throw Error(); } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a2185f591b6..fb0dbee5d9cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -197,5 +197,6 @@ jobs: uses: ./.github/actions/deploy-docs-site with: serviceKey: ${{ secrets.ANGULAR_DEV_SITE_DEPLOY }} + githubReleaseTrainReadToken: ${{ secrets.DOCS_DEPLOY_GITHUB_RELEASE_TRAIN_TOKEN }} configPath: 'adev/firebase.json' distDir: 'dist/bin/adev/build/browser' diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c65b0a131bb8..16cd32ecb193 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -39,7 +39,9 @@ jobs: - name: Check code format run: yarn ng-dev format changed --check ${{ github.event.pull_request.base.sha }} - name: Check Package Licenses - uses: angular/dev-infra/github-actions/linting/licenses@515b575bd6650a12353b6039b618cdb808280ad7 + uses: angular/dev-infra/github-actions/linting/licenses@56966c765e97f463c4795fac85d46543d1e6b342 + with: + allow-dependencies-licenses: 'pkg:npm/google-protobuf@' devtools: runs-on: ubuntu-latest @@ -93,7 +95,7 @@ jobs: - name: Run CI tests for framework run: yarn tsx ./scripts/build/build-packages-dist.mts - name: Archive build artifacts - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: pr-artifacts-${{ github.event.number }} path: dist/packages-dist/ diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 53ba48032426..09f3b5dd1b07 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -30,7 +30,7 @@ jobs: persist-credentials: false - name: 'Run analysis' - uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -47,6 +47,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11 + uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: sarif_file: results.sarif diff --git a/.pullapprove.yml b/.pullapprove.yml index 15817c859efb..84ef909b4b44 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -597,7 +597,8 @@ groups: conditions: - > contains_any_globs(files, [ - 'adev/**/{.*,*}' + 'adev/**/{.*,*}', + 'aio/**/{.*,*}' ]) reviewers: users: diff --git a/CHANGELOG.md b/CHANGELOG.md index bdbdaa390a31..84ebe80de0f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,158 @@ + +# 18.2.0-next.3 (2024-07-31) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [67e09404db](https://github.com/angular/angular/commit/67e09404db4a8a3a09bff005503a76f49d4fe055) | fix | reduce chance of conflicts between generated factory and local variables ([#57181](https://github.com/angular/angular/pull/57181)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [0f0a1f2836](https://github.com/angular/angular/commit/0f0a1f28365cdb2dc6abed5ec75d4f6ba7ff1578) | fix | emitting references to ngtypecheck files ([#57138](https://github.com/angular/angular/pull/57138)) | +| [e11c0c42d2](https://github.com/angular/angular/commit/e11c0c42d2cbcdf8a5d75a4e24a6a5dbed33943e) | fix | run JIT transforms on `@NgModule` classes with `jit: true` ([#57212](https://github.com/angular/angular/pull/57212)) | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [3459289ef0](https://github.com/angular/angular/commit/3459289ef079a80e84bb92e20c25fb6cae18aaf8) | feat | bootstrapModule can configure NgZone in providers ([#57060](https://github.com/angular/angular/pull/57060)) | +| [3a63c9ebbe](https://github.com/angular/angular/commit/3a63c9ebbec86ec13ba2c978dd3c497cd1f4ab46) | fix | errors during ApplicationRef.tick should be rethrown for zoneless tests ([#56993](https://github.com/angular/angular/pull/56993)) | +| [ca89ef9141](https://github.com/angular/angular/commit/ca89ef9141191d56415bdf62354f5125800a4039) | fix | handle shorthand assignment in the inject migration ([#57134](https://github.com/angular/angular/pull/57134)) | +| [a752178f28](https://github.com/angular/angular/commit/a752178f28b836acfc55c4dfa7cd4d18e99ca7c4) | fix | hydration error in some let declaration setups ([#57173](https://github.com/angular/angular/pull/57173)) | +| [2a4f488a6c](https://github.com/angular/angular/commit/2a4f488a6cb8bdadece70c8aa076c02fae801688) | fix | warnings for oversized images and lazy-lcp present with bootstrapModule ([#57060](https://github.com/angular/angular/pull/57060)) | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [147eee4253](https://github.com/angular/angular/commit/147eee42530b8e7d6a99f37c8eba7a38cbe29522) | feat | add migration to convert standalone component routes to be lazy loaded ([#56428](https://github.com/angular/angular/pull/56428)) | +| [cb442a0ce7](https://github.com/angular/angular/commit/cb442a0ce7183c7d0e315a58ade75aa09bdaf6dd) | fix | account for parameters with union types ([#57127](https://github.com/angular/angular/pull/57127)) | +| [166166d79e](https://github.com/angular/angular/commit/166166d79ebe2405989b869f96a04e1dee182666) | fix | add alias to inject migration ([#57127](https://github.com/angular/angular/pull/57127)) | +| [1cf616f671](https://github.com/angular/angular/commit/1cf616f6710d1324e24bc3421a1edc84c8bb1a02) | fix | remove generic arguments from the injected type reference ([#57127](https://github.com/angular/angular/pull/57127)) | +| [ba0df30ef6](https://github.com/angular/angular/commit/ba0df30ef617df0a8b6b7286f0147f7d1509330e) | fix | remove unused imports in inject migration ([#57179](https://github.com/angular/angular/pull/57179)) | +| [aae9646a1b](https://github.com/angular/angular/commit/aae9646a1b5a5ce114e624d9c1452d9f4c71b969) | fix | unwrap injected forwardRef ([#57127](https://github.com/angular/angular/pull/57127)) | + + + + +# 18.1.3 (2024-07-31) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [31dea066d6](https://github.com/angular/angular/commit/31dea066d636bb49fa18b1172815b1ef7af4dbe5) | fix | reduce chance of conflicts between generated factory and local variables ([#57181](https://github.com/angular/angular/pull/57181)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [1f9e090910](https://github.com/angular/angular/commit/1f9e09091076924a2f2c2d4bd6e5f65303bf8fea) | fix | emitting references to ngtypecheck files ([#57138](https://github.com/angular/angular/pull/57138)) ([#57202](https://github.com/angular/angular/pull/57202)) | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [f7ab04018e](https://github.com/angular/angular/commit/f7ab04018ea0fe12781de987fff40fd1dc30f3f0) | fix | errors during ApplicationRef.tick should be rethrown for zoneless tests ([#56993](https://github.com/angular/angular/pull/56993)) | +| [eaa83f9d27](https://github.com/angular/angular/commit/eaa83f9d279855b104597d396e39fe3496470daf) | fix | hydration error in some let declaration setups ([#57173](https://github.com/angular/angular/pull/57173)) | + + + + +# 18.2.0-next.2 (2024-07-24) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [08c5977bd5](https://github.com/angular/angular/commit/08c5977bd53caf7911e48f39fa4f60e5afd813de) | fix | limit the number of chained instructions ([#57069](https://github.com/angular/angular/pull/57069)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [d4ff6bc0b2](https://github.com/angular/angular/commit/d4ff6bc0b200f0a6bb095ea4a13e52b79e254cca) | fix | add warning for unused let declarations ([#57033](https://github.com/angular/angular/pull/57033)) | +| [6c2fbda694](https://github.com/angular/angular/commit/6c2fbda6942adbc7b21f3dfc1db0a42638223a1a) | fix | extended diagnostic visitor not visiting template attributes ([#57033](https://github.com/angular/angular/pull/57033)) | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [fab673a1dd](https://github.com/angular/angular/commit/fab673a1ddbca19ff9734f92a5ef0cc16be5708c) | feat | add ng generate schematic to convert to inject ([#57056](https://github.com/angular/angular/pull/57056)) | +| [7d4b2d2413](https://github.com/angular/angular/commit/7d4b2d2413935ca0869e659fc67dd88e00228593) | fix | `afterNextRender` hooks return that callback value. ([#57031](https://github.com/angular/angular/pull/57031)) | +| [8718abce90](https://github.com/angular/angular/commit/8718abce900617275d80ca56141d4e4436481b69) | fix | Deprecate ignoreChangesOutsideZone option ([#57029](https://github.com/angular/angular/pull/57029)) | +| [fe41b11434](https://github.com/angular/angular/commit/fe41b11434ad7bdff1c308fc31a6671e67c5ee29) | fix | tree shake dev mode error message ([#57035](https://github.com/angular/angular/pull/57035)) | + + + + +# 18.1.2 (2024-07-24) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [463945003d](https://github.com/angular/angular/commit/463945003dcf253c64809ffdcddabedb87e78e06) | fix | limit the number of chained instructions ([#57069](https://github.com/angular/angular/pull/57069)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [e904f34020](https://github.com/angular/angular/commit/e904f3402053ce9098854a31f2652020dd79e8f9) | fix | add warning for unused let declarations ([#57033](https://github.com/angular/angular/pull/57033)) | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [9e52c1c840](https://github.com/angular/angular/commit/9e52c1c8402dd5a54cf73caafff4a5b81d73fabd) | fix | `afterNextRender` hooks return that callback value. ([#57031](https://github.com/angular/angular/pull/57031)) | +| [b9fb98c67c](https://github.com/angular/angular/commit/b9fb98c67c29d8e5697b72788f09f1263e8130f4) | fix | tree shake dev mode error message ([#57035](https://github.com/angular/angular/pull/57035)) | + + + + +# 18.2.0-next.1 (2024-07-17) +### common +| Commit | Type | Description | +| -- | -- | -- | +| [2c4613a002](https://github.com/angular/angular/commit/2c4613a002d7670f8377cb53eaa9aca4bfc9521f) | fix | Don't run preconnect assertion on the server. ([#56213](https://github.com/angular/angular/pull/56213)) | +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [9167fc815c](https://github.com/angular/angular/commit/9167fc815c5bac5f39352dd13e381c5be84282c5) | fix | JIT mode incorrectly interpreting host directive configuration in partial compilation ([#57002](https://github.com/angular/angular/pull/57002)) | +| [107173c14d](https://github.com/angular/angular/commit/107173c14d1a0d95b78fbcac53a46bce5f8a6848) | fix | use strict equality for 'code' comparison ([#56944](https://github.com/angular/angular/pull/56944)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [98ed5b609e](https://github.com/angular/angular/commit/98ed5b609e76d3d2b464abfe49d70413c54d3eee) | feat | run JIT transform on classes with `jit: true` opt-out ([#56892](https://github.com/angular/angular/pull/56892)) | +| [f0d6d0688d](https://github.com/angular/angular/commit/f0d6d0688d984970e03d747405a9b11635ecdcf9) | fix | avoid emitting references to typecheck files in TS 5.4 ([#56961](https://github.com/angular/angular/pull/56961)) | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [e504ad97d4](https://github.com/angular/angular/commit/e504ad97d44159cf632ec987fce94f66bfddef37) | fix | not all callbacks running when registered at the same time ([#56981](https://github.com/angular/angular/pull/56981)) | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [4bb558ab0c](https://github.com/angular/angular/commit/4bb558ab0cbf2e5e34816377e977128a177a977a) | feat | support writing code refactorings ([#56895](https://github.com/angular/angular/pull/56895)) | +| [7663debce1](https://github.com/angular/angular/commit/7663debce1a8411a763a27b7cf8bc5955f8ea2ed) | perf | quick exit if no code fixes can exist ([#57000](https://github.com/angular/angular/pull/57000)) | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [0ea6a4a361](https://github.com/angular/angular/commit/0ea6a4a36128dc7a3792f4e164f024e91f429705) | fix | fix common module removal ([#56968](https://github.com/angular/angular/pull/56968)) | + + + + +# 18.1.1 (2024-07-17) +### common +| Commit | Type | Description | +| -- | -- | -- | +| [a1cb9dfc0d](https://github.com/angular/angular/commit/a1cb9dfc0d3539d16020a53dd4c32311240a6265) | fix | Don't run preconnect assertion on the server. ([#56213](https://github.com/angular/angular/pull/56213)) | +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [daf0317bdc](https://github.com/angular/angular/commit/daf0317bdcef79445eead4a3e0e1be75671f43fb) | fix | JIT mode incorrectly interpreting host directive configuration in partial compilation ([#57002](https://github.com/angular/angular/pull/57002)) | +| [d7dca6dbb6](https://github.com/angular/angular/commit/d7dca6dbb6d8afc77a988de0b7471ac4e078762e) | fix | use strict equality for 'code' comparison ([#56944](https://github.com/angular/angular/pull/56944)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [c94a897248](https://github.com/angular/angular/commit/c94a8972488f62656f5d0368b8763776e2cd39c6) | fix | avoid emitting references to typecheck files in TS 5.4 ([#56961](https://github.com/angular/angular/pull/56961)) | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [5682527d94](https://github.com/angular/angular/commit/5682527d949b078e92ff7588c7e25dad37fffc52) | fix | not all callbacks running when registered at the same time ([#56981](https://github.com/angular/angular/pull/56981)) | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [b666d2c20f](https://github.com/angular/angular/commit/b666d2c20f932f435fa6c51e1d74d7bca53381f2) | fix | fix common module removal ([#56968](https://github.com/angular/angular/pull/56968)) | + + + + +# 17.3.12 (2024-07-17) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [327bae473b](https://github.com/angular/angular/commit/327bae473b4ed430efefb4663312a01f59304081) | fix | JIT mode incorrectly interpreting host directive configuration in partial compilation ([#57002](https://github.com/angular/angular/pull/57002)) ([#57003](https://github.com/angular/angular/pull/57003)) | + + + # 18.2.0-next.0 (2024-07-10) ### compiler diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abf6b14449ec..e6ae080d3ff5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -353,9 +353,9 @@ DEPRECATED: Closes # ``` -Breaking Change section should start with the phrase "BREAKING CHANGE: " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions. +Breaking Change section should start with the phrase `BREAKING CHANGE: ` followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions. -Similarly, a Deprecation section should start with "DEPRECATED: " followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path. +Similarly, a Deprecation section should start with `DEPRECATED: ` followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path. ### Revert commits diff --git a/README.md b/README.md index 0a0fa102da9f..b004d505801e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ · Submit an Issue · - Blog + Blog

diff --git a/WORKSPACE b/WORKSPACE index 77bc236c54df..c2d8364029cc 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -75,7 +75,7 @@ yarn_install( "//:.yarnrc", "//:tools/npm-patches/@bazel+jasmine+5.8.1.patch", "//tools:postinstall-patches.js", - "//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-239d56b71911f9fa1eeefb6e4505dbe7b0cd81a7.patch", + "//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-d30a56c19bafaac67cf44e605ed8c2c0e45b0a51.patch", "//tools/esm-interop:patches/npm/@bazel+concatjs+5.8.1.patch", "//tools/esm-interop:patches/npm/@bazel+esbuild+5.7.1.patch", "//tools/esm-interop:patches/npm/@bazel+protractor+5.7.1.patch", @@ -143,10 +143,10 @@ cldr_xml_data_repository( # sass rules http_archive( name = "io_bazel_rules_sass", - sha256 = "510ac1fd1242e979a3dcb3f5c67273a94684e6c2a147e13b67cf34c62edebf91", - strip_prefix = "rules_sass-ea6b195390649fc7df822d76327e34b9123b7790", + sha256 = "cd83736ea65d0df064283aea5922dbaf132dd2b3aa54e7151aae7edaa9572c3e", + strip_prefix = "rules_sass-83022b98114c07e9588089c7fe8f76bc0262c7e7", urls = [ - "https://github.com/bazelbuild/rules_sass/archive/ea6b195390649fc7df822d76327e34b9123b7790.zip", + "https://github.com/bazelbuild/rules_sass/archive/83022b98114c07e9588089c7fe8f76bc0262c7e7.zip", ], ) diff --git a/adev/BUILD.bazel b/adev/BUILD.bazel index d7b3eca7300d..642032039132 100644 --- a/adev/BUILD.bazel +++ b/adev/BUILD.bazel @@ -26,6 +26,7 @@ APPLICATION_FILES = [ TEST_FILES = APPLICATION_FILES + [ "karma.conf.js", + "test-main.ts", "tsconfig.spec.json", ] + glob( ["**/*.spec.ts"], @@ -78,6 +79,9 @@ APPLICATION_DEPS = [ "@npm//@lezer/highlight", "@npm//@lezer/javascript", "@npm//@lezer/common", + "@npm//@stackblitz/sdk", + "@npm//open-in-idx", + "@npm//@webcontainer/api", "@npm//@xterm/xterm", "@npm//@xterm/addon-fit", "@npm//algoliasearch", diff --git a/adev/angular.json b/adev/angular.json index 696f80b6be04..cda2ce2a5ca5 100644 --- a/adev/angular.json +++ b/adev/angular.json @@ -84,10 +84,10 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "include": ["src/app"], "karmaConfig": "karma.conf.js", + "main": "test-main.ts", "inlineStyleLanguage": "scss", "assets": ["src/favicon.ico", "src/assets"], "styles": ["@angular/docs/styles/global-styles.scss"], diff --git a/adev/shared-docs/BUILD.bazel b/adev/shared-docs/BUILD.bazel new file mode 100644 index 000000000000..84daa513fdb8 --- /dev/null +++ b/adev/shared-docs/BUILD.bazel @@ -0,0 +1,49 @@ +load("//tools:defaults.bzl", "ng_module", "ng_package") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "docs", + srcs = [ + "index.ts", + ], + module_name = "@angular/docs", + deps = [ + "//adev/shared-docs/components", + "//adev/shared-docs/constants", + "//adev/shared-docs/directives", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/pipes", + "//adev/shared-docs/providers", + "//adev/shared-docs/services", + "//adev/shared-docs/utils", + ], +) + +ng_package( + name = "npm_package", + srcs = [ + "package.json", + "//adev/shared-docs/icons", + "//adev/shared-docs/pipeline:BUILD.bazel", + "//adev/shared-docs/pipeline:_guides.bzl", + "//adev/shared-docs/pipeline:_playground.bzl", + "//adev/shared-docs/pipeline:_stackblitz.bzl", + "//adev/shared-docs/pipeline:_tutorial.bzl", + "//adev/shared-docs/pipeline:guides.mjs", + "//adev/shared-docs/pipeline:guides-no-mermaid.mjs", + "//adev/shared-docs/pipeline:playground.mjs", + "//adev/shared-docs/pipeline:stackblitz.mjs", + "//adev/shared-docs/pipeline:tutorial.mjs", + "//adev/shared-docs/pipeline/examples/template:files", + "//adev/shared-docs/pipeline/tutorials/common:files", + "//adev/shared-docs/styles", + "//adev/shared-docs/testing", + ], + visibility = [ + "//adev:__pkg__", + ], + deps = [ + ":docs", + ], +) diff --git a/adev/shared-docs/components/BUILD.bazel b/adev/shared-docs/components/BUILD.bazel new file mode 100644 index 000000000000..b67b6951f8ed --- /dev/null +++ b/adev/shared-docs/components/BUILD.bazel @@ -0,0 +1,27 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "components", + srcs = [ + "index.ts", + ], + visibility = [ + "//adev/shared-docs:__pkg__", + ], + deps = [ + "//adev/shared-docs/components/algolia-icon", + "//adev/shared-docs/components/breadcrumb", + "//adev/shared-docs/components/cookie-popup", + "//adev/shared-docs/components/copy-source-code-button", + "//adev/shared-docs/components/icon", + "//adev/shared-docs/components/navigation-list", + "//adev/shared-docs/components/search-dialog", + "//adev/shared-docs/components/select", + "//adev/shared-docs/components/slide-toggle", + "//adev/shared-docs/components/table-of-contents", + "//adev/shared-docs/components/text-field", + "//adev/shared-docs/components/viewers", + ], +) diff --git a/adev/shared-docs/components/algolia-icon/BUILD.bazel b/adev/shared-docs/components/algolia-icon/BUILD.bazel new file mode 100644 index 000000000000..4f7585f5d9f4 --- /dev/null +++ b/adev/shared-docs/components/algolia-icon/BUILD.bazel @@ -0,0 +1,20 @@ +load("//tools:defaults.bzl", "ng_module") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "algolia-icon", + srcs = [ + "algolia-icon.component.ts", + ], + assets = [ + "algolia-icon.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + "//adev/shared-docs/components/search-dialog:__pkg__", + ], + deps = [ + "//packages/core", + ], +) diff --git a/adev/shared-docs/components/algolia-icon/algolia-icon.component.html b/adev/shared-docs/components/algolia-icon/algolia-icon.component.html new file mode 100644 index 000000000000..8f5ca876db8b --- /dev/null +++ b/adev/shared-docs/components/algolia-icon/algolia-icon.component.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + diff --git a/adev/shared-docs/components/algolia-icon/algolia-icon.component.ts b/adev/shared-docs/components/algolia-icon/algolia-icon.component.ts new file mode 100644 index 000000000000..e6ef6973255e --- /dev/null +++ b/adev/shared-docs/components/algolia-icon/algolia-icon.component.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ChangeDetectionStrategy, Component} from '@angular/core'; + +@Component({ + selector: 'docs-algolia-icon', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [], + templateUrl: './algolia-icon.component.html', +}) +export class AlgoliaIcon {} diff --git a/adev/shared-docs/components/breadcrumb/BUILD.bazel b/adev/shared-docs/components/breadcrumb/BUILD.bazel new file mode 100644 index 000000000000..d236106f078b --- /dev/null +++ b/adev/shared-docs/components/breadcrumb/BUILD.bazel @@ -0,0 +1,54 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "breadcrumb", + srcs = [ + "breadcrumb.component.ts", + ], + assets = [ + ":breadcrumb.component.css", + "breadcrumb.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + "//adev/shared-docs/components/viewers:__pkg__", + ], + deps = [ + "//adev/shared-docs/interfaces", + "//adev/shared-docs/services", + "//packages/common", + "//packages/core", + "//packages/router", + ], +) + +sass_binary( + name = "style", + src = "breadcrumb.component.scss", +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":breadcrumb", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/services", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "//packages/router", + "//packages/router/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/breadcrumb/breadcrumb.component.html b/adev/shared-docs/components/breadcrumb/breadcrumb.component.html new file mode 100644 index 000000000000..5cf1378ed228 --- /dev/null +++ b/adev/shared-docs/components/breadcrumb/breadcrumb.component.html @@ -0,0 +1,13 @@ +@for (breadcrumb of breadcrumbItems(); track breadcrumb) { +
+ @if (breadcrumb.path) { + @if (breadcrumb.isExternal) { + {{ breadcrumb.label }} + } @else { + {{ breadcrumb.label }} + } + } @else { + {{ breadcrumb.label }} + } +
+} diff --git a/adev/shared-docs/components/breadcrumb/breadcrumb.component.scss b/adev/shared-docs/components/breadcrumb/breadcrumb.component.scss new file mode 100644 index 000000000000..fc5e6889d6b1 --- /dev/null +++ b/adev/shared-docs/components/breadcrumb/breadcrumb.component.scss @@ -0,0 +1,25 @@ +:host { + display: flex; + align-items: center; + padding-block-end: 1.5rem; +} + +.docs-breadcrumb { + span { + color: var(--quaternary-contrast); + font-size: 0.875rem; + display: flex; + align-items: center; + } + + &:not(:last-child) { + span { + &::after { + content: 'chevron_right'; + font-family: var(--icons); + margin-inline: 0.5rem; + color: var(--quinary-contrast); + } + } + } +} diff --git a/adev/shared-docs/components/breadcrumb/breadcrumb.component.spec.ts b/adev/shared-docs/components/breadcrumb/breadcrumb.component.spec.ts new file mode 100644 index 000000000000..8a524477e518 --- /dev/null +++ b/adev/shared-docs/components/breadcrumb/breadcrumb.component.spec.ts @@ -0,0 +1,83 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {Breadcrumb} from './breadcrumb.component'; +import {NavigationState} from '../../services'; +import {NavigationItem} from '../../interfaces'; +import {By} from '@angular/platform-browser'; +import {RouterTestingModule} from '@angular/router/testing'; +import {provideExperimentalZonelessChangeDetection} from '@angular/core'; + +describe('Breadcrumb', () => { + let fixture: ComponentFixture; + let navigationStateSpy: jasmine.SpyObj; + + beforeEach(() => { + navigationStateSpy = jasmine.createSpyObj('NavigationState', ['activeNavigationItem']); + + TestBed.configureTestingModule({ + imports: [Breadcrumb, RouterTestingModule], + providers: [ + provideExperimentalZonelessChangeDetection(), + { + provide: NavigationState, + useValue: navigationStateSpy, + }, + ], + }); + fixture = TestBed.createComponent(Breadcrumb); + }); + + it('should display proper breadcrumb structure based on navigation state', () => { + navigationStateSpy.activeNavigationItem.and.returnValue(item); + + fixture.detectChanges(); + const breadcrumbs = fixture.debugElement.queryAll(By.css('.docs-breadcrumb span')); + + expect(breadcrumbs.length).toBe(2); + expect(breadcrumbs[0].nativeElement.innerText).toEqual('Grandparent'); + expect(breadcrumbs[1].nativeElement.innerText).toEqual('Parent'); + }); + + it('should display breadcrumb links when navigation item has got path', () => { + navigationStateSpy.activeNavigationItem.and.returnValue(exampleItemWithPath); + + fixture.detectChanges(); + const breadcrumbs = fixture.debugElement.queryAll(By.css('.docs-breadcrumb a')); + + expect(breadcrumbs.length).toBe(1); + expect(breadcrumbs[0].nativeElement.innerText).toEqual('Parent'); + expect(breadcrumbs[0].nativeElement.href).toEqual(`${window.origin}/example`); + }); +}); + +const grandparent: NavigationItem = { + label: 'Grandparent', +}; + +const parent: NavigationItem = { + label: 'Parent', + parent: grandparent, +}; + +const item: NavigationItem = { + label: 'Active Item', + parent: parent, +}; + +const parentWithPath: NavigationItem = { + label: 'Parent', + path: '/example', +}; + +const exampleItemWithPath: NavigationItem = { + label: 'Active Item', + parent: parentWithPath, +}; diff --git a/adev/shared-docs/components/breadcrumb/breadcrumb.component.ts b/adev/shared-docs/components/breadcrumb/breadcrumb.component.ts new file mode 100644 index 000000000000..db7caaa268cc --- /dev/null +++ b/adev/shared-docs/components/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,50 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ChangeDetectionStrategy, Component, OnInit, inject, signal} from '@angular/core'; +import {NavigationState} from '../../services/index'; +import {NavigationItem} from '../../interfaces/index'; +import {NgFor, NgIf} from '@angular/common'; +import {RouterLink} from '@angular/router'; + +@Component({ + selector: 'docs-breadcrumb', + standalone: true, + imports: [NgIf, NgFor, RouterLink], + templateUrl: './breadcrumb.component.html', + styleUrls: ['./breadcrumb.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Breadcrumb implements OnInit { + private readonly navigationState = inject(NavigationState); + + breadcrumbItems = signal([]); + + ngOnInit(): void { + this.setBreadcrumbItemsBasedOnNavigationStructure(); + } + + private setBreadcrumbItemsBasedOnNavigationStructure(): void { + let breadcrumbs: NavigationItem[] = []; + + const traverse = (node: NavigationItem | null) => { + if (!node) { + return; + } + + if (node.parent) { + breadcrumbs = [node.parent, ...breadcrumbs]; + traverse(node.parent); + } + }; + + traverse(this.navigationState.activeNavigationItem()); + + this.breadcrumbItems.set(breadcrumbs); + } +} diff --git a/adev/shared-docs/components/cookie-popup/BUILD.bazel b/adev/shared-docs/components/cookie-popup/BUILD.bazel new file mode 100644 index 000000000000..1ae91a5bbb9d --- /dev/null +++ b/adev/shared-docs/components/cookie-popup/BUILD.bazel @@ -0,0 +1,49 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "cookie-popup", + srcs = [ + "cookie-popup.component.ts", + ], + assets = [ + ":cookie-popup.component.css", + "cookie-popup.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + ], + deps = [ + "//adev/shared-docs/providers", + "//adev/shared-docs/utils", + "//packages/common", + "//packages/core", + ], +) + +sass_binary( + name = "style", + src = "cookie-popup.component.scss", +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":cookie-popup", + "//adev/shared-docs/providers", + "//adev/shared-docs/testing", + "//packages/core", + "//packages/core/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/cookie-popup/cookie-popup.component.html b/adev/shared-docs/components/cookie-popup/cookie-popup.component.html new file mode 100644 index 000000000000..808d146175ba --- /dev/null +++ b/adev/shared-docs/components/cookie-popup/cookie-popup.component.html @@ -0,0 +1,22 @@ +@if (!hasAccepted()) { +
+

This site uses cookies from Google to deliver its services and to analyze traffic.

+ +
+ + + + +
+
+} diff --git a/adev/shared-docs/components/cookie-popup/cookie-popup.component.scss b/adev/shared-docs/components/cookie-popup/cookie-popup.component.scss new file mode 100644 index 000000000000..65fab67d4e73 --- /dev/null +++ b/adev/shared-docs/components/cookie-popup/cookie-popup.component.scss @@ -0,0 +1,40 @@ +:host { + position: fixed; + bottom: 0.5rem; + right: 0.5rem; + z-index: var(--z-index-cookie-consent); + opacity: 0; + visibility: hidden; + animation: 1s linear forwards 0.5s fadeIn; +} + +.docs-cookies-popup { + padding: 1rem; + background-color: var(--page-background); + border: 1px solid var(--senary-contrast); + border-radius: 0.25rem; + font-size: 0.875rem; + max-width: 265px; + transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + + > div { + display: flex; + gap: 0.5rem; + align-items: center; + width: 100%; + margin-block-start: 1rem; + } + + p { + margin-block: 0; + color: var(--primary-contrast); + } +} + +@keyframes fadeIn { + 100% { + opacity: 100%; + visibility: visible; + } +} diff --git a/adev/shared-docs/components/cookie-popup/cookie-popup.component.spec.ts b/adev/shared-docs/components/cookie-popup/cookie-popup.component.spec.ts new file mode 100644 index 000000000000..b1dc899bad8f --- /dev/null +++ b/adev/shared-docs/components/cookie-popup/cookie-popup.component.spec.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {CookiePopup, STORAGE_KEY} from './cookie-popup.component'; +import {LOCAL_STORAGE} from '../../providers/index'; +import {MockLocalStorage} from '../../testing/index'; +import {provideExperimentalZonelessChangeDetection} from '@angular/core'; + +describe('CookiePopup', () => { + let fixture: ComponentFixture; + let mockLocalStorage = new MockLocalStorage(); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CookiePopup], + providers: [ + provideExperimentalZonelessChangeDetection(), + { + provide: LOCAL_STORAGE, + useValue: mockLocalStorage, + }, + ], + }); + }); + + it('should make the popup visible by default', () => { + initComponent(false); + + expect(getCookiesPopup()).not.toBeNull(); + }); + + it('should hide the cookies popup if the user has already accepted cookies', () => { + initComponent(true); + + expect(getCookiesPopup()).toBeNull(); + }); + + it('should hide the cookies popup', () => { + initComponent(false); + + accept(); + + fixture.detectChanges(); + + expect(getCookiesPopup()).toBeNull(); + }); + + it('should store the user confirmation', () => { + initComponent(false); + + expect(mockLocalStorage.getItem(STORAGE_KEY)).toBeNull(); + + accept(); + + expect(mockLocalStorage.getItem(STORAGE_KEY)).toBe('true'); + }); + + // Helpers + function getCookiesPopup() { + return (fixture.nativeElement as HTMLElement).querySelector('.docs-cookies-popup'); + } + + function accept() { + (fixture.nativeElement as HTMLElement) + .querySelector('button[text="Ok, Got it"]') + ?.click(); + } + + function initComponent(cookiesAccepted: boolean) { + mockLocalStorage.setItem(STORAGE_KEY, cookiesAccepted ? 'true' : null); + fixture = TestBed.createComponent(CookiePopup); + + fixture.detectChanges(); + } +}); diff --git a/adev/shared-docs/components/cookie-popup/cookie-popup.component.ts b/adev/shared-docs/components/cookie-popup/cookie-popup.component.ts new file mode 100644 index 000000000000..49a481650f44 --- /dev/null +++ b/adev/shared-docs/components/cookie-popup/cookie-popup.component.ts @@ -0,0 +1,56 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core'; +import {NgIf} from '@angular/common'; +import {LOCAL_STORAGE} from '../../providers/index'; +import {setCookieConsent} from '../../utils'; + +/** + * Decelare gtag as part of the window in this file as gtag is expected to already be loaded. + */ +declare const window: Window & typeof globalThis & {gtag?: Function}; + +export const STORAGE_KEY = 'docs-accepts-cookies'; + +@Component({ + selector: 'docs-cookie-popup', + standalone: true, + imports: [NgIf], + templateUrl: './cookie-popup.component.html', + styleUrls: ['./cookie-popup.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CookiePopup { + private readonly localStorage = inject(LOCAL_STORAGE); + + /** Whether the user has accepted the cookie disclaimer. */ + hasAccepted = signal(false); + + constructor() { + // Needs to be in a try/catch, because some browsers will + // throw when using `localStorage` in private mode. + try { + this.hasAccepted.set(this.localStorage?.getItem(STORAGE_KEY) === 'true'); + } catch { + this.hasAccepted.set(false); + } + } + + /** Accepts the cookie disclaimer. */ + protected accept(): void { + try { + this.localStorage?.setItem(STORAGE_KEY, 'true'); + } catch {} + + this.hasAccepted.set(true); + + // Enable Google Analytics consent properties + setCookieConsent('granted'); + } +} diff --git a/adev/shared-docs/components/copy-source-code-button/BUILD.bazel b/adev/shared-docs/components/copy-source-code-button/BUILD.bazel new file mode 100644 index 000000000000..56d0e95b4930 --- /dev/null +++ b/adev/shared-docs/components/copy-source-code-button/BUILD.bazel @@ -0,0 +1,43 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "copy-source-code-button", + srcs = [ + "copy-source-code-button.component.ts", + ], + assets = [ + "copy-source-code-button.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + "//adev/shared-docs/components/viewers:__pkg__", + ], + deps = [ + "//adev/shared-docs/components/icon", + "//packages/common", + "//packages/core", + "@npm//@angular/cdk", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":copy-source-code-button", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "@npm//@angular/cdk", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.html b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.html new file mode 100644 index 000000000000..3feab56ac302 --- /dev/null +++ b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.html @@ -0,0 +1,18 @@ + + + + +check diff --git a/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.spec.ts b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.spec.ts new file mode 100644 index 000000000000..e8f430c72b19 --- /dev/null +++ b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.spec.ts @@ -0,0 +1,127 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; + +import { + CONFIRMATION_DISPLAY_TIME_MS, + CopySourceCodeButton, +} from './copy-source-code-button.component'; +import { + ChangeDetectionStrategy, + Component, + provideExperimentalZonelessChangeDetection, + signal, +} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {Clipboard} from '@angular/cdk/clipboard'; + +const SUCCESSFULLY_COPY_CLASS_NAME = 'docs-copy-source-code-button-success'; +const FAILED_COPY_CLASS_NAME = 'docs-copy-source-code-button-failed'; + +describe('CopySourceCodeButton', () => { + let component: CodeSnippetWrapper; + let fixture: ComponentFixture; + let copySpy: jasmine.Spy<(text: string) => boolean>; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [CodeSnippetWrapper], + providers: [provideExperimentalZonelessChangeDetection()], + }); + fixture = TestBed.createComponent(CodeSnippetWrapper); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + beforeEach(() => { + const clipboardService = TestBed.inject(Clipboard); + copySpy = spyOn(clipboardService, 'copy'); + }); + + it('should call clipboard service when clicked on copy source code', async () => { + const expectedCodeToBeCopied = 'npm install -g @angular/cli'; + component.code.set(expectedCodeToBeCopied); + + await fixture.whenStable(); + + const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement; + button.click(); + + expect(copySpy.calls.argsFor(0)[0].trim()).toBe(expectedCodeToBeCopied); + }); + + it('should not copy lines marked as deleted when code snippet contains diff', async () => { + const codeInHtmlFormat = ` + +
<div *ngFor="let product of products">
+
<div *ngFor="let product of products()">
+
+ `; + const expectedCodeToBeCopied = `
`; + component.code.set(codeInHtmlFormat); + + await fixture.whenStable(); + + const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement; + button.click(); + + expect(copySpy.calls.argsFor(0)[0].trim()).toBe(expectedCodeToBeCopied); + }); + + it(`should set ${SUCCESSFULLY_COPY_CLASS_NAME} for ${CONFIRMATION_DISPLAY_TIME_MS} ms when copy was executed properly`, fakeAsync(() => { + component.code.set('example'); + + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement; + button.click(); + fixture.detectChanges(); + + expect(button).toHaveClass(SUCCESSFULLY_COPY_CLASS_NAME); + + tick(CONFIRMATION_DISPLAY_TIME_MS); + fixture.detectChanges(); + + expect(button).not.toHaveClass(SUCCESSFULLY_COPY_CLASS_NAME); + })); + + it(`should set ${FAILED_COPY_CLASS_NAME} for ${CONFIRMATION_DISPLAY_TIME_MS} ms when copy failed`, fakeAsync(() => { + component.code.set('example'); + copySpy.and.throwError('Fake copy error'); + + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement; + button.click(); + + fixture.detectChanges(); + + expect(button).toHaveClass(FAILED_COPY_CLASS_NAME); + + tick(CONFIRMATION_DISPLAY_TIME_MS); + fixture.detectChanges(); + + expect(button).not.toHaveClass(FAILED_COPY_CLASS_NAME); + })); +}); + +@Component({ + template: ` +
+      
+    
+ + `, + imports: [CopySourceCodeButton], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, +}) +class CodeSnippetWrapper { + code = signal(''); +} diff --git a/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.ts b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.ts new file mode 100644 index 000000000000..9e11c2764f5d --- /dev/null +++ b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.ts @@ -0,0 +1,89 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + WritableSignal, + inject, + signal, +} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {Clipboard} from '@angular/cdk/clipboard'; +import {IconComponent} from '../icon/icon.component'; + +export const REMOVED_LINE_CLASS_NAME = '.line.remove'; +export const CONFIRMATION_DISPLAY_TIME_MS = 2000; + +@Component({ + selector: 'button[docs-copy-source-code]', + standalone: true, + imports: [CommonModule, IconComponent], + templateUrl: './copy-source-code-button.component.html', + host: { + 'type': 'button', + 'aria-label': 'Copy example source to clipboard', + 'title': 'Copy example source', + '(click)': 'copySourceCode()', + '[class.docs-copy-source-code-button-success]': 'showCopySuccess()', + '[class.docs-copy-source-code-button-failed]': 'showCopyFailure()', + }, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CopySourceCodeButton { + private readonly changeDetector = inject(ChangeDetectorRef); + private readonly clipboard = inject(Clipboard); + private readonly elementRef = inject(ElementRef); + + protected readonly showCopySuccess = signal(false); + protected readonly showCopyFailure = signal(false); + + copySourceCode(): void { + try { + const codeElement = this.elementRef.nativeElement.parentElement.querySelector( + 'code', + ) as HTMLElement; + const sourceCode = this.getSourceCode(codeElement); + this.clipboard.copy(sourceCode); + this.showResult(this.showCopySuccess); + } catch { + this.showResult(this.showCopyFailure); + } + } + + private getSourceCode(codeElement: HTMLElement): string { + this.showCopySuccess.set(false); + this.showCopyFailure.set(false); + + const removedLines: NodeList = codeElement.querySelectorAll(REMOVED_LINE_CLASS_NAME); + + if (removedLines.length) { + // Get only those lines which are not marked as removed + const formattedText = Array.from(codeElement.querySelectorAll('.line:not(.remove)')) + .map((line) => (line as HTMLDivElement).innerText) + .join('\n'); + + return formattedText.trim(); + } else { + const text: string = codeElement.innerText || ''; + return text.replace(/\n\n\n/g, ``).trim(); + } + } + + private showResult(messageState: WritableSignal) { + messageState.set(true); + + setTimeout(() => { + messageState.set(false); + // It's required for code snippets embedded in the ExampleViewer. + this.changeDetector.markForCheck(); + }, CONFIRMATION_DISPLAY_TIME_MS); + } +} diff --git a/adev/shared-docs/components/icon/BUILD.bazel b/adev/shared-docs/components/icon/BUILD.bazel new file mode 100644 index 000000000000..c7a794921bd1 --- /dev/null +++ b/adev/shared-docs/components/icon/BUILD.bazel @@ -0,0 +1,32 @@ +load("//tools:defaults.bzl", "ng_module") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "icon", + srcs = [ + "icon.component.ts", + ], + assets = [ + ":icon.component.css", + "icon.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + "//adev/shared-docs/components/copy-source-code-button:__pkg__", + "//adev/shared-docs/components/navigation-list:__pkg__", + "//adev/shared-docs/components/table-of-contents:__pkg__", + "//adev/shared-docs/components/text-field:__pkg__", + "//adev/shared-docs/components/viewers:__pkg__", + ], + deps = [ + "//packages/common", + "//packages/core", + ], +) + +sass_binary( + name = "style", + src = "icon.component.scss", +) diff --git a/adev/shared-docs/components/icon/icon.component.html b/adev/shared-docs/components/icon/icon.component.html new file mode 100644 index 000000000000..6dbc74306383 --- /dev/null +++ b/adev/shared-docs/components/icon/icon.component.html @@ -0,0 +1 @@ + diff --git a/adev/shared-docs/components/icon/icon.component.scss b/adev/shared-docs/components/icon/icon.component.scss new file mode 100644 index 000000000000..a5337d3d774d --- /dev/null +++ b/adev/shared-docs/components/icon/icon.component.scss @@ -0,0 +1,3 @@ +.docs-icon_high-contrast { + color: var(--primary-contrast); +} diff --git a/adev/shared-docs/components/icon/icon.component.ts b/adev/shared-docs/components/icon/icon.component.ts new file mode 100644 index 000000000000..452a9064ac68 --- /dev/null +++ b/adev/shared-docs/components/icon/icon.component.ts @@ -0,0 +1,55 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {DOCUMENT} from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + afterNextRender, + computed, + inject, + signal, +} from '@angular/core'; + +@Component({ + selector: 'docs-icon', + standalone: true, + templateUrl: './icon.component.html', + styleUrl: './icon.component.scss', + host: { + '[class]': 'MATERIAL_SYMBOLS_OUTLINED', + '[style.font-size.px]': 'fontSize()', + 'aria-hidden': 'true', + 'translate': 'no', + }, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IconComponent { + fontSize = computed(() => { + return IconComponent.isFontLoaded() ? null : 0; + }); + + protected readonly MATERIAL_SYMBOLS_OUTLINED = 'material-symbols-outlined'; + + private static isFontLoaded = signal(false); + /** Share the same promise across different instances of the component */ + private static whenFontLoad?: Promise | undefined; + + constructor() { + if (IconComponent.isFontLoaded()) { + return; + } + + const document = inject(DOCUMENT); + afterNextRender(async () => { + IconComponent.whenFontLoad ??= document.fonts.load('normal 1px "Material Symbols Outlined"'); + await IconComponent.whenFontLoad; + IconComponent.isFontLoaded.set(true); + }); + } +} diff --git a/adev/shared-docs/components/index.ts b/adev/shared-docs/components/index.ts new file mode 100644 index 000000000000..6acb4ad37a40 --- /dev/null +++ b/adev/shared-docs/components/index.ts @@ -0,0 +1,17 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './viewers/docs-viewer/docs-viewer.component'; +export * from './cookie-popup/cookie-popup.component'; +export * from './navigation-list/navigation-list.component'; +export * from './select/select.component'; +export * from './slide-toggle/slide-toggle.component'; +export * from './table-of-contents/table-of-contents.component'; +export * from './text-field/text-field.component'; +export * from './icon/icon.component'; +export * from './search-dialog/search-dialog.component'; diff --git a/adev/shared-docs/components/navigation-list/BUILD.bazel b/adev/shared-docs/components/navigation-list/BUILD.bazel new file mode 100644 index 000000000000..fbc9b896386b --- /dev/null +++ b/adev/shared-docs/components/navigation-list/BUILD.bazel @@ -0,0 +1,58 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "navigation-list", + srcs = [ + "navigation-list.component.ts", + ], + assets = [ + ":navigation-list.component.css", + "navigation-list.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + ], + deps = [ + "//adev/shared-docs/components/icon", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/pipes", + "//adev/shared-docs/services", + "//packages/common", + "//packages/core", + "//packages/router", + ], +) + +sass_binary( + name = "style", + src = "navigation-list.component.scss", + deps = [ + "//adev/shared-docs/styles", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":navigation-list", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/services", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "//packages/router", + "//packages/router/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.html b/adev/shared-docs/components/navigation-list/navigation-list.component.html new file mode 100644 index 000000000000..66987a0dac46 --- /dev/null +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.html @@ -0,0 +1,76 @@ + + + + + diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.scss b/adev/shared-docs/components/navigation-list/navigation-list.component.scss new file mode 100644 index 000000000000..2df35c706eb8 --- /dev/null +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.scss @@ -0,0 +1,148 @@ +@use '../../styles/media-queries' as mq; + +:host { + display: flex; + min-width: var(--secondary-nav-width); + list-style: none; + overflow-y: auto; + overflow-x: hidden; + height: 100vh; + padding: 0; + margin: 0; + padding-block: 1.5rem; + font-size: 0.875rem; + box-sizing: border-box; + + &::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); + cursor: pointer; + } + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--septenary-contrast); + @include mq.for-tablet-landscape-down { + background-color: var(--quinary-contrast); + } + + border-radius: 10px; + transition: background-color 0.3s ease; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--quinary-contrast); + } + + .docs-nav-secondary & { + padding-block: 2rem; + } + + > .docs-faceted-list { + border: 0; + } + + .docs-navigation-link-hidden { + display: none; + } + + .docs-nav-item-has-icon { + &::after { + // FIXME: for some reason this disappears when transformed + content: 'chevron_right'; + font-size: 1.25rem; + font-family: var(--icons); + } + } +} + +.docs-secondary-nav-header { + padding-block: 1.25rem; + font-weight: 500; +} + +.docs-secondary-nav-button { + width: 15rem; + display: flex; + justify-content: space-between; + align-items: center; + border: none; + padding-block: 1.25rem; + padding-inline-start: 0; + color: var(--primary-contrast); + font-size: 0.875rem; + font-family: var(--inter-font); + line-height: 160%; + letter-spacing: -0.00875rem; + transition: color 0.3s ease, background 0.3s ease; + text-align: left; // forces left alignment of text in button + + &.docs-secondary-nav-button-active { + // font gradient + background-image: var(--pink-to-purple-vertical-gradient); + &::before { + opacity: 1; + transform: scaleY(1); + background: var(--pink-to-purple-vertical-gradient); + } + &:hover { + &::before { + opacity: 1; + transform: scaleY(1.1); + } + } + } +} + +.docs-expanded-button { + justify-content: start; + gap: 0.5rem; +} + +a, +.docs-not-expanded-button { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: 500; + line-height: 1.4rem; + letter-spacing: -0.00875rem; + padding: 0.5rem; + padding-inline-start: 1rem; + text-align: left; +} + +// Add padding-bottom to last item in the list +.docs-navigation-list { + width: 100%; + + li:last-of-type { + ul:last-of-type { + li:last-of-type { + padding-block-end: 1rem; + } + } + } + &:first-child { + margin-inline-start: 1rem; + } +} + +.docs-external-link { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + gap: 0.5rem; + &::after { + content: 'open_in_new'; + font-family: var(--icons); + font-size: 1.1rem; + color: var(--quinary-contrast); + transition: color 0.3s ease; + margin-inline-end: 0.4rem; + } +} diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.spec.ts b/adev/shared-docs/components/navigation-list/navigation-list.component.spec.ts new file mode 100644 index 000000000000..b1d58bbc86b9 --- /dev/null +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.spec.ts @@ -0,0 +1,147 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {NavigationList} from './navigation-list.component'; +import {By} from '@angular/platform-browser'; +import {NavigationItem} from '../../interfaces'; +import {RouterTestingModule} from '@angular/router/testing'; +import {provideExperimentalZonelessChangeDetection, signal} from '@angular/core'; +import {NavigationState} from '../../services'; + +const navigationItems: NavigationItem[] = [ + { + label: 'Introduction', + path: 'guide', + level: 1, + }, + { + label: 'Getting Started', + level: 1, + children: [ + {label: 'What is Angular?', path: 'guide/what-is-angular', level: 2}, + {label: 'Setup', path: 'guide/setup', level: 2}, + ], + }, +]; + +describe('NavigationList', () => { + let component: NavigationList; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NavigationList, RouterTestingModule], + providers: [ + {provide: NavigationState, useClass: FakeNavigationListState}, + provideExperimentalZonelessChangeDetection(), + ], + }).compileComponents(); + fixture = TestBed.createComponent(NavigationList); + component = fixture.componentInstance; + }); + + it('should display provided navigation structure', () => { + component.navigationItems = [...navigationItems]; + fixture.detectChanges(); + + const links = fixture.debugElement.queryAll(By.css('a')); + const nonClickableItem = fixture.debugElement.queryAll(By.css('.docs-secondary-nav-header')); + + expect(links.length).toBe(3); + expect(nonClickableItem.length).toBe(1); + }); + + it('should append `docs-navigation-list-dropdown` when isDropdownView is true', () => { + component.isDropdownView = true; + fixture.detectChanges(); + + const ulElement = fixture.debugElement.query(By.css('ul.docs-navigation-list-dropdown')); + + expect(ulElement).toBeTruthy(); + }); + + it('should not append `docs-navigation-list-dropdown` when isDropdownView is false', () => { + component.isDropdownView = false; + fixture.detectChanges(); + + const ulElement = fixture.debugElement.query(By.css('ul.docs-navigation-list-dropdown')); + + expect(ulElement).toBeFalsy(); + }); + + it('should emit linkClicked when user clicked on link', () => { + const emitClickOnLinkSpy = spyOn(component, 'emitClickOnLink'); + component.navigationItems = [...navigationItems]; + fixture.detectChanges(true); + + const guideLink = fixture.debugElement.query(By.css('a[href="/guide"]')); + guideLink.nativeElement.click(); + + expect(emitClickOnLinkSpy).toHaveBeenCalledTimes(1); + }); + + it(`should not call navigationState.toggleItem() when item's level is equal to 1 and is not neither expandable or collapsable level`, () => { + const navigationState = TestBed.inject(NavigationState); + const toggleItemSpy = spyOn(navigationState, 'toggleItem'); + const itemToToggle = navigationItems[1]; + + component.toggle(itemToToggle); + + expect(toggleItemSpy).not.toHaveBeenCalled(); + }); + + it(`should call navigationState.toggleItem() when item's level is expandable`, () => { + const navigationState = TestBed.inject(NavigationState); + const toggleItemSpy = spyOn(navigationState, 'toggleItem'); + const itemToToggle = navigationItems[1]; + + component.expandableLevel = 1; + component.toggle(itemToToggle); + + expect(toggleItemSpy).toHaveBeenCalledOnceWith(itemToToggle); + }); + + it(`should call navigationState.toggleItem() when item's level is collapsable`, () => { + const navigationState = TestBed.inject(NavigationState); + const toggleItemSpy = spyOn(navigationState, 'toggleItem'); + const itemToToggle = navigationItems[1].children![1]; + + component.collapsableLevel = 2; + component.toggle(itemToToggle); + + expect(toggleItemSpy).toHaveBeenCalledOnceWith(itemToToggle); + }); + + it('should display items to provided level', () => { + component.navigationItems = [...navigationItems]; + component.displayItemsToLevel = 1; + fixture.detectChanges(true); + + const visibleItems = fixture.debugElement.queryAll( + By.css('li.docs-faceted-list-item:not(.docs-navigation-link-hidden)'), + ); + const hiddenItems = fixture.debugElement.queryAll( + By.css('li.docs-faceted-list-item.docs-navigation-link-hidden'), + ); + + expect(visibleItems.length).toBe(2); + expect(visibleItems[0].nativeElement.innerText).toBe(navigationItems[0].label); + expect(visibleItems[1].nativeElement.innerText).toBe(navigationItems[1].label); + expect(hiddenItems.length).toBe(2); + expect(hiddenItems[0].nativeElement.innerText).toBe(navigationItems[1].children![0].label); + expect(hiddenItems[1].nativeElement.innerText).toBe(navigationItems[1].children![1].label); + }); +}); + +class FakeNavigationListState { + isOpened = signal(true); + activeNavigationItem = signal(navigationItems.at(1)); + toggleItem(item: NavigationItem) {} +} diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.ts b/adev/shared-docs/components/navigation-list/navigation-list.component.ts new file mode 100644 index 000000000000..de29e03e66d3 --- /dev/null +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.ts @@ -0,0 +1,60 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, + inject, +} from '@angular/core'; +import {NavigationItem} from '../../interfaces/index'; +import {NavigationState} from '../../services/index'; +import {RouterLink, RouterLinkActive} from '@angular/router'; +import {CommonModule} from '@angular/common'; +import {IconComponent} from '../icon/icon.component'; +import {IsActiveNavigationItem} from '../../pipes/is-active-navigation-item.pipe'; + +@Component({ + selector: 'docs-navigation-list', + standalone: true, + imports: [CommonModule, RouterLink, RouterLinkActive, IconComponent, IsActiveNavigationItem], + templateUrl: './navigation-list.component.html', + styleUrls: ['./navigation-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NavigationList { + @Input({required: true}) navigationItems: NavigationItem[] = []; + @Input() displayItemsToLevel: number = 2; + @Input() collapsableLevel: number | undefined = undefined; + @Input() expandableLevel: number = 2; + @Input() isDropdownView = false; + + @Output() linkClicked = new EventEmitter(); + + private readonly navigationState = inject(NavigationState); + + expandedItems = this.navigationState.expandedItems; + activeItem = this.navigationState.activeNavigationItem; + + toggle(item: NavigationItem): void { + if ( + item.level === 1 && + item.level !== this.expandableLevel && + item.level !== this.collapsableLevel + ) { + return; + } + this.navigationState.toggleItem(item); + } + + emitClickOnLink(): void { + this.linkClicked.emit(); + } +} diff --git a/adev/shared-docs/components/search-dialog/BUILD.bazel b/adev/shared-docs/components/search-dialog/BUILD.bazel new file mode 100644 index 000000000000..6b054fd472aa --- /dev/null +++ b/adev/shared-docs/components/search-dialog/BUILD.bazel @@ -0,0 +1,59 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "search-dialog", + srcs = [ + "search-dialog.component.ts", + ], + assets = [ + ":search-dialog.component.css", + "search-dialog.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + ], + deps = [ + "//adev/shared-docs/components/algolia-icon", + "//adev/shared-docs/components/text-field", + "//adev/shared-docs/directives", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/pipes", + "//adev/shared-docs/services", + "//packages/core", + "//packages/forms", + "//packages/router", + ], +) + +sass_binary( + name = "style", + src = "search-dialog.component.scss", +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":search-dialog", + "//adev/shared-docs/components/algolia-icon", + "//adev/shared-docs/providers", + "//adev/shared-docs/services", + "//adev/shared-docs/testing", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "//packages/router", + "//packages/router/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.html b/adev/shared-docs/components/search-dialog/search-dialog.component.html new file mode 100644 index 000000000000..e60cbf98934a --- /dev/null +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.html @@ -0,0 +1,81 @@ + +
+ + + @if (searchResults() && searchResults()!.length > 0) { + + } @else { +
+ @if (searchResults() === undefined) { +
+ Start typing to see results +
+ } @else if (searchResults()?.length === 0) { +
+ No results found +
+ } +
+ } + +
+ Search by + + + +
+
+
diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.scss b/adev/shared-docs/components/search-dialog/search-dialog.component.scss new file mode 100644 index 000000000000..fc74df4f6c0b --- /dev/null +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.scss @@ -0,0 +1,147 @@ +dialog { + background-color: transparent; + border: none; + padding-block-end: 3rem; + + &::backdrop { + backdrop-filter: blur(5px); + } +} + +.docs-search-container { + width: 750px; + max-width: 90vw; + background-color: var(--page-background); + border: 1px solid var(--senary-contrast); + border-radius: 0.25rem; + box-sizing: border-box; + + .docs-search-input { + border-radius: 0.25rem 0.25rem 0 0; + border: none; + border-block-end: 1px solid var(--senary-contrast); + height: 2.6875rem; // 43px; + padding-inline-start: 1rem; + position: relative; + + &::after { + content: 'Esc'; + position: absolute; + right: 1rem; + color: var(--gray-400); + font-size: 0.875rem; + } + } + + ul { + max-height: 50vh; + overflow-y: auto; + list-style-type: none; + padding-inline: 0; + padding-block-start: 1rem; + margin: 0; + border-block-end: 1px solid var(--senary-contrast); + + li { + border-inline-start: 2px solid var(--senary-contrast); + margin-inline-start: 1rem; + padding-inline-end: 1rem; + padding-block: 0.25rem; + + a { + color: var(--secondary-contrast); + display: flex; + justify-content: space-between; + gap: 0.5rem; + + .docs-search-result-icon { + i { + display: flex; + align-items: center; + font-size: 1.2rem; + } + } + } + + &.active { + background-color: var(--septenary-contrast); // stylelint-disable-line + } + + &:hover, + &.active { + background-color: var(--octonary-contrast); // stylelint-disable-line + border-inline-start: 2px solid var(--primary-contrast); + a { + span:not(.docs-result-page-title), + .docs-search-results__type { + color: var(--primary-contrast); + i { + color: var(--primary-contrast); + } + } + } + } + } + + .docs-search-result-icon, + .docs-search-results__type, + .docs-result-page-title { + color: var(--quaternary-contrast); + display: inline-block; + font-size: 0.875rem; + transition: color 0.3s ease; + padding: 0.75rem; + padding-inline-end: 0; + } + + .docs-search-results__lvl2 { + display: inline-block; + margin-inline-start: 2rem; + padding-block-start: 0; + } + + .docs-search-results__lvl3 { + margin-inline-start: 2rem; + padding-block-start: 0; + } + } + + .docs-result-page-title { + font-size: 0.875rem; + font-weight: 400; + } +} + +.docs-search-results__start-typing, +.docs-search-results__no-results { + padding: 0.75rem; + color: var(--gray-400); +} + +.docs-result-icon-and-type { + display: flex; + + .docs-search-results__type { + padding-inline-start: 0; + } +} + +.docs-algolia { + display: flex; + align-items: center; + justify-content: end; + color: var(--gray-400); + padding: 1rem; + font-size: 0.75rem; + font-weight: 500; + gap: 0.25rem; + background-color: var(--page-background); + border-radius: 0 0 0.25rem 0.25rem; + + docs-algolia-icon { + display: inline-flex; + margin-block-start: 0.12rem; + margin-inline-start: 0.15rem; + width: 4rem; + } +} diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts b/adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts new file mode 100644 index 000000000000..9d96bf0ef1b1 --- /dev/null +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts @@ -0,0 +1,152 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {SearchDialog} from './search-dialog.component'; +import {WINDOW} from '../../providers'; +import {Search} from '../../services'; +import {FakeEventTarget} from '../../testing/index'; +import {By} from '@angular/platform-browser'; +import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component'; +import {RouterTestingModule} from '@angular/router/testing'; +import {Router} from '@angular/router'; +import {provideExperimentalZonelessChangeDetection} from '@angular/core'; + +describe('SearchDialog', () => { + let fixture: ComponentFixture; + + const fakeSearch = { + searchQuery: jasmine.createSpy(), + searchResults: jasmine.createSpy(), + }; + const fakeWindow = new FakeEventTarget(); + + beforeEach(async () => { + fakeSearch.searchResults.and.returnValue([]); + fakeSearch.searchQuery.and.returnValue(''); + + await TestBed.configureTestingModule({ + imports: [SearchDialog, RouterTestingModule], + providers: [ + provideExperimentalZonelessChangeDetection(), + { + provide: Search, + useValue: fakeSearch, + }, + { + provide: WINDOW, + useValue: fakeWindow, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchDialog); + fixture.detectChanges(); + }); + + it('should navigate to active item when user pressed Enter', () => { + const router = TestBed.inject(Router); + const navigateByUrlSpy = spyOn(router, 'navigateByUrl'); + + fakeSearch.searchResults.and.returnValue(fakeSearchResults); + fixture.detectChanges(); + + fakeWindow.dispatchEvent( + new KeyboardEvent('keydown', { + code: 'Enter', + key: 'Enter', + charCode: 13, + keyCode: 13, + view: window, + bubbles: true, + }), + ); + + expect(navigateByUrlSpy).toHaveBeenCalledOnceWith('fakeUrl1#h1'); + }); + + it('should always display algolia logo', () => { + const algoliaIcon = fixture.debugElement.query(By.directive(AlgoliaIcon)); + + expect(algoliaIcon).toBeTruthy(); + }); + + it('should display `No results found` message when there are no results for provided query', () => { + fakeSearch.searchResults.and.returnValue([]); + fixture.detectChanges(); + + const noResultsContainer = fixture.debugElement.query( + By.css('.docs-search-results__no-results'), + ); + + expect(noResultsContainer).toBeTruthy(); + }); + + it('should display `Start typing to see results` message when there are no provided query', () => { + fakeSearch.searchResults.and.returnValue(undefined); + fixture.detectChanges(); + + const startTypingContainer = fixture.debugElement.query( + By.css('.docs-search-results__start-typing'), + ); + + expect(startTypingContainer).toBeTruthy(); + }); + + it('should display list of the search results when results exist', () => { + fakeSearch.searchResults.and.returnValue(fakeSearchResults); + fixture.detectChanges(); + + const resultListContainer = fixture.debugElement.query(By.css('ul.docs-search-results')); + const resultItems = fixture.debugElement.queryAll(By.css('ul.docs-search-results li a')); + + expect(resultListContainer).toBeTruthy(); + expect(resultItems.length).toBe(2); + expect(resultItems[0].nativeElement.href).toBe(`${window.origin}/fakeUrl1#h1`); + expect(resultItems[1].nativeElement.href).toBe(`${window.origin}/fakeUrl2#h1`); + }); + + it('should close search dialog when user clicked outside `.docs-search-container`', () => { + const dialogContainer = fixture.debugElement.query(By.css('dialog')); + const closeSearchDialogSpy = spyOn(fixture.componentInstance, 'closeSearchDialog'); + + dialogContainer.nativeElement.click(); + + expect(closeSearchDialogSpy).toHaveBeenCalled(); + }); +}); + +const fakeSearchResults = [ + { + 'url': 'https://angular.dev/fakeUrl1#h1', + 'hierarchy': { + 'lvl0': 'FakeLvl0', + 'lvl1': 'FakeLvl1', + 'lvl2': 'FakeLvl2', + 'lvl3': null, + 'lvl4': null, + 'lvl5': null, + 'lvl6': null, + }, + 'objectID': 'fakeObjectId1', + }, + { + 'url': 'https://angular.dev/fakeUrl2#h1', + 'hierarchy': { + 'lvl0': 'FakeLvl0', + 'lvl1': 'FakeLvl1', + 'lvl2': 'FakeLvl2', + 'lvl3': null, + 'lvl4': null, + 'lvl5': null, + 'lvl6': null, + }, + 'objectID': 'fakeObjectId2', + }, +]; diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.ts b/adev/shared-docs/components/search-dialog/search-dialog.component.ts new file mode 100644 index 000000000000..e47595bb6337 --- /dev/null +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.ts @@ -0,0 +1,132 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + ChangeDetectionStrategy, + Component, + ElementRef, + Injector, + OnDestroy, + Signal, + afterNextRender, + effect, + inject, + output, + viewChild, + viewChildren, +} from '@angular/core'; + +import {WINDOW} from '../../providers/index'; +import {ClickOutside} from '../../directives/index'; +import {Search} from '../../services/index'; + +import {TextField} from '../text-field/text-field.component'; +import {FormsModule} from '@angular/forms'; +import {ActiveDescendantKeyManager} from '@angular/cdk/a11y'; +import {SearchItem} from '../../directives/search-item/search-item.directive'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {Router, RouterLink} from '@angular/router'; +import {filter, fromEvent} from 'rxjs'; +import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component'; +import {RelativeLink} from '../../pipes/relative-link.pipe'; + +@Component({ + selector: 'docs-search-dialog', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + ClickOutside, + TextField, + FormsModule, + SearchItem, + AlgoliaIcon, + RelativeLink, + RouterLink, + ], + templateUrl: './search-dialog.component.html', + styleUrls: ['./search-dialog.component.scss'], +}) +export class SearchDialog implements OnDestroy { + onClose = output(); + dialog = viewChild.required('searchDialog', {read: ElementRef}) as Signal< + ElementRef + >; + items = viewChildren(SearchItem); + + private readonly search = inject(Search); + private readonly relativeLink = new RelativeLink(); + private readonly router = inject(Router); + private readonly window = inject(WINDOW); + private readonly injector = inject(Injector); + private readonly keyManager = new ActiveDescendantKeyManager( + this.items, + this.injector, + ).withWrap(); + + searchQuery = this.search.searchQuery; + searchResults = this.search.searchResults; + + constructor() { + effect(() => { + this.items(); + afterNextRender( + { + write: () => this.keyManager.setFirstItemActive(), + }, + {injector: this.injector}, + ); + }); + + this.keyManager.change.pipe(takeUntilDestroyed()).subscribe(() => { + this.keyManager.activeItem?.scrollIntoView(); + }); + + afterNextRender({ + write: () => { + if (!this.dialog().nativeElement.open) { + this.dialog().nativeElement.showModal?.(); + } + }, + }); + + fromEvent(this.window, 'keydown') + .pipe(takeUntilDestroyed()) + .subscribe((event) => { + // When user presses Enter we can navigate to currently selected item in the search result list. + if (event.key === 'Enter') { + this.navigateToTheActiveItem(); + } else { + this.keyManager.onKeydown(event); + } + }); + } + + ngOnDestroy(): void { + this.keyManager.destroy(); + } + + closeSearchDialog() { + this.dialog().nativeElement.close(); + this.onClose.emit(); + } + + updateSearchQuery(query: string) { + this.search.updateSearchQuery(query); + } + + private navigateToTheActiveItem(): void { + const activeItemLink: string | undefined = this.keyManager.activeItem?.item?.url; + + if (!activeItemLink) { + return; + } + + this.router.navigateByUrl(this.relativeLink.transform(activeItemLink)); + this.onClose.emit(); + } +} diff --git a/adev/shared-docs/components/select/BUILD.bazel b/adev/shared-docs/components/select/BUILD.bazel new file mode 100644 index 000000000000..eeeebfefeca0 --- /dev/null +++ b/adev/shared-docs/components/select/BUILD.bazel @@ -0,0 +1,46 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "select", + srcs = [ + "select.component.ts", + ], + assets = [ + ":select.component.css", + "select.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + ], + deps = [ + "//packages/common", + "//packages/core", + "//packages/forms", + ], +) + +sass_binary( + name = "style", + src = "select.component.scss", +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":select", + "//packages/core", + "//packages/core/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/select/select.component.html b/adev/shared-docs/components/select/select.component.html new file mode 100644 index 000000000000..872a145a3d54 --- /dev/null +++ b/adev/shared-docs/components/select/select.component.html @@ -0,0 +1,5 @@ + diff --git a/adev/shared-docs/components/select/select.component.scss b/adev/shared-docs/components/select/select.component.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/adev/shared-docs/components/select/select.component.spec.ts b/adev/shared-docs/components/select/select.component.spec.ts new file mode 100644 index 000000000000..720c9321fead --- /dev/null +++ b/adev/shared-docs/components/select/select.component.spec.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {Select} from './select.component'; +import {provideExperimentalZonelessChangeDetection} from '@angular/core'; + +describe('Select', () => { + let component: Select; + let fixture: ComponentFixture + +
+ diff --git a/adev/shared-docs/components/slide-toggle/slide-toggle.component.scss b/adev/shared-docs/components/slide-toggle/slide-toggle.component.scss new file mode 100644 index 000000000000..c9173149f0ec --- /dev/null +++ b/adev/shared-docs/components/slide-toggle/slide-toggle.component.scss @@ -0,0 +1,78 @@ +:host, +label { + display: inline-flex; + gap: 0.5em; + align-items: center; +} + +.docs-label { + font-size: 0.875rem; + font-style: normal; + font-weight: 500; + line-height: 160%; // 1.4rem + letter-spacing: -0.00875rem; + color: var(--quaternary-contrast); +} + +.docs-toggle { + position: relative; + display: inline-block; + + width: 3rem; + height: 1.5rem; + border: 1px solid var(--senary-contrast); + border-radius: 34px; + + input { + opacity: 0; + width: 0; + height: 0; + } +} + +.docs-slider { + position: absolute; + cursor: pointer; + border-radius: 34px; + inset: 0; + background-color: var(--septenary-contrast); + transition: background-color 0.3s ease, border-color 0.3s ease; + + // background + &::before { + content: ''; + position: absolute; + inset: 0; + border-radius: 34px; + background: var(--pink-to-purple-horizontal-gradient); + opacity: 0; + transition: opacity 0.3s ease; + } + + // toggle knob + &::after { + position: absolute; + content: ''; + height: 1.25rem; + width: 1.25rem; + left: 0.125rem; + bottom: 0.125rem; + background-color: var(--page-background); + transition: transform 0.3s ease, background-color 0.3s ease; + border-radius: 50%; + } +} + +input { + &:checked + .docs-slider { + // background + &::before { + opacity: 1; + } + + // toggle knob + &::after { + transform: translateX(1.5rem); + } + } +} diff --git a/adev/shared-docs/components/slide-toggle/slide-toggle.component.spec.ts b/adev/shared-docs/components/slide-toggle/slide-toggle.component.spec.ts new file mode 100644 index 000000000000..398999464659 --- /dev/null +++ b/adev/shared-docs/components/slide-toggle/slide-toggle.component.spec.ts @@ -0,0 +1,60 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {SlideToggle} from './slide-toggle.component'; +import {provideExperimentalZonelessChangeDetection} from '@angular/core'; + +describe('SlideToggle', () => { + let component: SlideToggle; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SlideToggle], + providers: [provideExperimentalZonelessChangeDetection()], + }); + fixture = TestBed.createComponent(SlideToggle); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should toggle the value when clicked', () => { + expect(component['checked']()).toBeFalse(); + + const buttonElement = fixture.nativeElement.querySelector('input'); + buttonElement.click(); + + expect(component['checked']()).toBeTrue(); + }); + + it('should call onChange and onTouched when toggled', () => { + const onChangeSpy = jasmine.createSpy('onChangeSpy'); + const onTouchedSpy = jasmine.createSpy('onTouchedSpy'); + component.registerOnChange(onChangeSpy); + component.registerOnTouched(onTouchedSpy); + + component.toggle(); + + expect(onChangeSpy).toHaveBeenCalled(); + expect(onChangeSpy).toHaveBeenCalledWith(true); + expect(onTouchedSpy).toHaveBeenCalled(); + }); + + it('should set active class for button when is checked', () => { + component.writeValue(true); + fixture.detectChanges(); + const buttonElement: HTMLButtonElement = fixture.nativeElement.querySelector('input'); + expect(buttonElement.classList.contains('docs-toggle-active')).toBeTrue(); + + component.writeValue(false); + fixture.detectChanges(); + expect(buttonElement.classList.contains('docs-toggle-active')).toBeFalse(); + }); +}); diff --git a/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts b/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts new file mode 100644 index 000000000000..d6ce5be6839e --- /dev/null +++ b/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts @@ -0,0 +1,69 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ChangeDetectionStrategy, Component, Input, forwardRef, signal} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; + +@Component({ + selector: 'docs-slide-toggle', + standalone: true, + imports: [CommonModule], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './slide-toggle.component.html', + styleUrls: ['./slide-toggle.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SlideToggle), + multi: true, + }, + ], +}) +export class SlideToggle implements ControlValueAccessor { + @Input({required: true}) buttonId!: string; + @Input({required: true}) label!: string; + @Input() disabled = false; + + // Implemented as part of ControlValueAccessor. + private onChange: (value: boolean) => void = (_: boolean) => {}; + private onTouched: () => void = () => {}; + + protected readonly checked = signal(false); + + // Implemented as part of ControlValueAccessor. + writeValue(value: boolean): void { + this.checked.set(value); + } + + // Implemented as part of ControlValueAccessor. + registerOnChange(fn: any): void { + this.onChange = fn; + } + + // Implemented as part of ControlValueAccessor. + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + // Implemented as part of ControlValueAccessor. + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + // Toggles the checked state of the slide-toggle. + toggle(): void { + if (this.disabled) { + return; + } + + this.checked.update((checked) => !checked); + this.onChange(this.checked()); + this.onTouched(); + } +} diff --git a/adev/shared-docs/components/table-of-contents/BUILD.bazel b/adev/shared-docs/components/table-of-contents/BUILD.bazel new file mode 100644 index 000000000000..5a78168cc860 --- /dev/null +++ b/adev/shared-docs/components/table-of-contents/BUILD.bazel @@ -0,0 +1,55 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "table-of-contents", + srcs = [ + "table-of-contents.component.ts", + ], + assets = [ + ":table-of-contents.component.css", + "table-of-contents.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + "//adev/shared-docs/components/viewers:__pkg__", + ], + deps = [ + "//adev/shared-docs/components/icon", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/services", + "//packages/common", + "//packages/core", + "//packages/router", + ], +) + +sass_binary( + name = "style", + src = "table-of-contents.component.scss", +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":table-of-contents", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/providers", + "//adev/shared-docs/services", + "//packages/core", + "//packages/core/testing", + "//packages/router", + "//packages/router/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/table-of-contents/table-of-contents.component.html b/adev/shared-docs/components/table-of-contents/table-of-contents.component.html new file mode 100644 index 000000000000..f5b514649f14 --- /dev/null +++ b/adev/shared-docs/components/table-of-contents/table-of-contents.component.html @@ -0,0 +1,31 @@ + diff --git a/adev/shared-docs/components/table-of-contents/table-of-contents.component.scss b/adev/shared-docs/components/table-of-contents/table-of-contents.component.scss new file mode 100644 index 000000000000..46abaf355c20 --- /dev/null +++ b/adev/shared-docs/components/table-of-contents/table-of-contents.component.scss @@ -0,0 +1,96 @@ +:host { + display: flex; + flex-direction: column; + position: fixed; + right: 16px; + top: 0; + height: fit-content; + width: 14rem; + padding-inline: 1rem; + max-height: 100vh; + overflow-y: scroll; + + aside { + margin-bottom: 2rem; + } + + & :has(ul li:only-child) { + // Hide the entire TOC is there's only one item + display: none; + } + + @media only screen and (max-width: 1430px) { + position: relative; + right: 0; + max-height: min-content; + width: 100%; + } + + .docs-title { + font-size: 1.25rem; + margin-block-start: var(--layout-padding); + } + + &::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); + cursor: pointer; + } + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--septenary-contrast); + border-radius: 10px; + transition: background-color 0.3s ease; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--quinary-contrast); + } + + .docs-faceted-list-item { + font-size: 0.875rem; + + a { + display: block; // to prevent overflow from the li parent + padding: 0.5rem 0.5rem 0.5rem 1rem; + font-weight: 500; + } + + &.docs-toc-item-h3 a { + padding-inline-start: 2rem; + } + } +} + +button { + background: transparent; + border: none; + font-size: 0.875rem; + font-family: var(--inter-font); + display: flex; + align-items: center; + margin: 0.5rem 0; + color: var(--tertiary-contrast); + transition: color 0.3s ease; + cursor: pointer; + + docs-icon { + margin-inline-end: 0.35rem; + opacity: 0.6; + transition: opacity 0.3s ease; + } + + &:hover { + docs-icon { + opacity: 1; + } + } + + @media only screen and (max-width: 1430px) { + display: none; + } +} diff --git a/adev/shared-docs/components/table-of-contents/table-of-contents.component.spec.ts b/adev/shared-docs/components/table-of-contents/table-of-contents.component.spec.ts new file mode 100644 index 000000000000..1f838b6fad4f --- /dev/null +++ b/adev/shared-docs/components/table-of-contents/table-of-contents.component.spec.ts @@ -0,0 +1,119 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {TableOfContents} from './table-of-contents.component'; +import {RouterTestingModule} from '@angular/router/testing'; +import {TableOfContentsItem, TableOfContentsLevel} from '../../interfaces/index'; +import {TableOfContentsScrollSpy, TableOfContentsLoader} from '../../services/index'; +import {WINDOW} from '../../providers/index'; +import {provideExperimentalZonelessChangeDetection, signal} from '@angular/core'; + +describe('TableOfContents', () => { + let component: TableOfContents; + let fixture: ComponentFixture; + let scrollSpy: jasmine.SpyObj; + const items: TableOfContentsItem[] = [ + { + title: 'Heading 2', + top: 0, + id: 'item-heading-2', + level: TableOfContentsLevel.H2, + }, + { + title: 'First Heading 3', + top: 100, + id: 'first-item-heading-3', + level: TableOfContentsLevel.H3, + }, + { + title: 'Second Heading 3', + top: 200, + id: 'second-item-heading-3', + level: TableOfContentsLevel.H3, + }, + ]; + const fakeWindow = { + addEventListener: () => {}, + removeEventListener: () => {}, + }; + + beforeEach(async () => { + scrollSpy = jasmine.createSpyObj('TableOfContentsScrollSpy', [ + 'startListeningToScroll', + 'activeItemId', + 'scrollbarThumbOnTop', + ]); + scrollSpy.startListeningToScroll.and.returnValue(); + scrollSpy.activeItemId.and.returnValue(items[0].id); + scrollSpy.scrollbarThumbOnTop.and.returnValue(false); + + await TestBed.configureTestingModule({ + imports: [TableOfContents, RouterTestingModule], + providers: [ + provideExperimentalZonelessChangeDetection(), + { + provide: WINDOW, + useValue: fakeWindow, + }, + ], + }).compileComponents(); + TestBed.overrideProvider(TableOfContentsScrollSpy, { + useValue: scrollSpy, + }); + + const tableOfContentsLoaderSpy = TestBed.inject(TableOfContentsLoader); + spyOn(tableOfContentsLoaderSpy, 'buildTableOfContent').and.returnValue(); + tableOfContentsLoaderSpy.tableOfContentItems.set(items); + fixture = TestBed.createComponent(TableOfContents); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call scrollToTop when user click on Back to the top button', () => { + const spy = spyOn(component, 'scrollToTop'); + + fixture.detectChanges(); + + const button: HTMLButtonElement = fixture.nativeElement.querySelector('button'); + button.click(); + + expect(spy).toHaveBeenCalledOnceWith(); + }); + + it('should render items when tableOfContentItems has value', () => { + fixture.detectChanges(); + + const renderedItems = fixture.nativeElement.querySelectorAll('li'); + + expect(renderedItems.length).toBe(3); + expect(component.tableOfContentItems().length).toBe(3); + }); + + it('should append level class to element', () => { + fixture.detectChanges(); + + const h2Items = fixture.nativeElement.querySelectorAll('li.docs-toc-item-h2'); + const h3Items = fixture.nativeElement.querySelectorAll('li.docs-toc-item-h3'); + + expect(h2Items.length).toBe(1); + expect(h3Items.length).toBe(2); + }); + + it('should append active class when item is active', () => { + fixture.detectChanges(); + + const activeItem = fixture.nativeElement.querySelector('.docs-faceted-list-item-active'); + + expect(activeItem).toBeTruthy(); + }); +}); diff --git a/adev/shared-docs/components/table-of-contents/table-of-contents.component.ts b/adev/shared-docs/components/table-of-contents/table-of-contents.component.ts new file mode 100644 index 000000000000..c658ff777142 --- /dev/null +++ b/adev/shared-docs/components/table-of-contents/table-of-contents.component.ts @@ -0,0 +1,45 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {NgFor, NgIf} from '@angular/common'; +import {ChangeDetectionStrategy, Component, Input, computed, inject} from '@angular/core'; +import {RouterLink} from '@angular/router'; +import {TableOfContentsLevel} from '../../interfaces/index'; +import {TableOfContentsLoader} from '../../services/table-of-contents-loader.service'; +import {TableOfContentsScrollSpy} from '../../services/table-of-contents-scroll-spy.service'; +import {IconComponent} from '../icon/icon.component'; + +@Component({ + selector: 'docs-table-of-contents', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './table-of-contents.component.html', + styleUrls: ['./table-of-contents.component.scss'], + imports: [NgIf, NgFor, RouterLink, IconComponent], +}) +export class TableOfContents { + // Element that contains the content from which the Table of Contents is built + @Input({required: true}) contentSourceElement!: HTMLElement; + + private readonly scrollSpy = inject(TableOfContentsScrollSpy); + private readonly tableOfContentsLoader = inject(TableOfContentsLoader); + tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems; + + activeItemId = this.scrollSpy.activeItemId; + shouldDisplayScrollToTop = computed(() => !this.scrollSpy.scrollbarThumbOnTop()); + TableOfContentsLevel = TableOfContentsLevel; + + ngAfterViewInit() { + this.tableOfContentsLoader.buildTableOfContent(this.contentSourceElement); + this.scrollSpy.startListeningToScroll(this.contentSourceElement); + } + + scrollToTop(): void { + this.scrollSpy.scrollToTop(); + } +} diff --git a/adev/shared-docs/components/text-field/BUILD.bazel b/adev/shared-docs/components/text-field/BUILD.bazel new file mode 100644 index 000000000000..84ad42e6607e --- /dev/null +++ b/adev/shared-docs/components/text-field/BUILD.bazel @@ -0,0 +1,48 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "text-field", + srcs = [ + "text-field.component.ts", + ], + assets = [ + ":text-field.component.css", + "text-field.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + "//adev/shared-docs/components/search-dialog:__pkg__", + ], + deps = [ + "//adev/shared-docs/components/icon", + "//packages/common", + "//packages/core", + "//packages/forms", + ], +) + +sass_binary( + name = "style", + src = "text-field.component.scss", +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":text-field", + "//packages/core", + "//packages/core/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/text-field/text-field.component.html b/adev/shared-docs/components/text-field/text-field.component.html new file mode 100644 index 000000000000..521a133e99eb --- /dev/null +++ b/adev/shared-docs/components/text-field/text-field.component.html @@ -0,0 +1,12 @@ +@if (!hideIcon) { +search +} + diff --git a/adev/shared-docs/components/text-field/text-field.component.scss b/adev/shared-docs/components/text-field/text-field.component.scss new file mode 100644 index 000000000000..12795d772f59 --- /dev/null +++ b/adev/shared-docs/components/text-field/text-field.component.scss @@ -0,0 +1,9 @@ +// search field +.docs-text-field { + font-size: 1.125rem; +} + +// filter field on api reference list +docs-icon + .docs-text-field { + font-size: 1rem; +} diff --git a/adev/shared-docs/components/text-field/text-field.component.spec.ts b/adev/shared-docs/components/text-field/text-field.component.spec.ts new file mode 100644 index 000000000000..c79ee80780eb --- /dev/null +++ b/adev/shared-docs/components/text-field/text-field.component.spec.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {TextField} from './text-field.component'; +import {provideExperimentalZonelessChangeDetection} from '@angular/core'; + +describe('TextField', () => { + let component: TextField; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TextField], + providers: [provideExperimentalZonelessChangeDetection()], + }); + fixture = TestBed.createComponent(TextField); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/adev/shared-docs/components/text-field/text-field.component.ts b/adev/shared-docs/components/text-field/text-field.component.ts new file mode 100644 index 000000000000..2b7aebc980ce --- /dev/null +++ b/adev/shared-docs/components/text-field/text-field.component.ts @@ -0,0 +1,93 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + ChangeDetectionStrategy, + Component, + ElementRef, + Input, + ViewChild, + afterNextRender, + forwardRef, + signal, +} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {IconComponent} from '../icon/icon.component'; + +@Component({ + selector: 'docs-text-field', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, FormsModule, IconComponent], + templateUrl: './text-field.component.html', + styleUrls: ['./text-field.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TextField), + multi: true, + }, + ], + host: { + class: 'docs-form-element', + }, +}) +export class TextField implements ControlValueAccessor { + @ViewChild('inputRef') private input?: ElementRef; + + @Input() name: string | null = null; + @Input() placeholder: string | null = null; + @Input() disabled = false; + @Input() hideIcon = false; + @Input() autofocus = false; + + // Implemented as part of ControlValueAccessor. + private onChange: (value: string) => void = (_: string) => {}; + private onTouched: () => void = () => {}; + + protected readonly value = signal(null); + + constructor() { + afterNextRender(() => { + if (this.autofocus) { + this.input?.nativeElement.focus(); + } + }); + } + + // Implemented as part of ControlValueAccessor. + writeValue(value: string): void { + this.value.set(value); + } + + // Implemented as part of ControlValueAccessor. + registerOnChange(fn: any): void { + this.onChange = fn; + } + + // Implemented as part of ControlValueAccessor. + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + // Implemented as part of ControlValueAccessor. + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + setValue(value: string): void { + if (this.disabled) { + return; + } + + this.value.set(value); + this.onChange(value); + this.onTouched(); + } +} diff --git a/adev/shared-docs/components/viewers/BUILD.bazel b/adev/shared-docs/components/viewers/BUILD.bazel new file mode 100644 index 000000000000..facc63a6c971 --- /dev/null +++ b/adev/shared-docs/components/viewers/BUILD.bazel @@ -0,0 +1,76 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "viewers", + srcs = [ + "docs-viewer/docs-viewer.component.ts", + "example-viewer/example-viewer.component.ts", + ], + assets = [ + ":docs-viewer/docs-viewer.component.css", + ":example-viewer/example-viewer.component.css", + "example-viewer/example-viewer.component.html", + ], + visibility = [ + "//adev/shared-docs/components:__pkg__", + ], + deps = [ + "//adev/shared-docs/components/breadcrumb", + "//adev/shared-docs/components/copy-source-code-button", + "//adev/shared-docs/components/table-of-contents", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/providers", + "//packages/common", + "//packages/core", + "//packages/router", + "@npm//@angular/cdk", + "@npm//@angular/material", + "@npm//@types/dom-view-transitions", + ], +) + +sass_binary( + name = "example-viewer-style", + src = "example-viewer/example-viewer.component.scss", +) + +sass_binary( + name = "docs-viewer-style", + src = "docs-viewer/docs-viewer.component.scss", + deps = [ + "//adev/shared-docs/styles", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["**/*.spec.ts"], + ), + deps = [ + ":viewers", + "//adev/shared-docs/components/breadcrumb", + "//adev/shared-docs/components/copy-source-code-button", + "//adev/shared-docs/components/icon", + "//adev/shared-docs/components/table-of-contents", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/providers", + "//adev/shared-docs/services", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "//packages/platform-browser/animations", + "//packages/router", + "@npm//@angular/cdk", + "@npm//@angular/material", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.scss b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.scss new file mode 100644 index 000000000000..6f67101eb0d2 --- /dev/null +++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.scss @@ -0,0 +1,129 @@ +@use '../../../styles/links' as links; +@use '../../../styles/anchor' as anchor; + +:host { + --translate-y: clamp(5px, 0.25em, 7px); +} + +.docs-viewer { + display: flex; + flex-direction: column; + padding: var(--layout-padding); + max-width: var(--page-width); + width: 100%; + box-sizing: border-box; + + @media only screen and (max-width: 1430px) { + container: docs-content / inline-size; + } + + // If rendered on the docs page, accommodate width for TOC + docs-docs & { + @media only screen and (min-width: 1430px) and (max-width: 1550px) { + width: calc(100% - 195px - var(--layout-padding)); + max-width: var(--page-width); + } + } + + pre { + margin-block: 0; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + .docs-anchor { + margin-block-start: 2.5rem; + display: inline-block; + color: inherit; + + @include anchor.docs-anchor(); + } + } + + h1 { + font-size: 2.5rem; + margin-block-end: 0; + } + + h2 { + font-size: 2rem; + margin-block-end: 0.5rem; + } + + h3 { + font-size: 1.5rem; + margin-block-end: 0.5rem; + } + + h4 { + font-size: 1.25rem; + margin-block-end: 0.5rem; + } + + h5 { + font-size: 1rem; + margin-block-end: 0; + } + + h6 { + font-size: 0.875rem; + margin-block-end: 0; + } + + > :last-child { + margin-block-end: 0; + } + + a:not(.docs-github-links):not(.docs-card):not(.docs-pill):not(.docs-example-github-link) { + &[href^='http:'], + &[href^='https:'] { + @include links.external-link-with-icon(); + } + } + + &-scroll-margin-large { + h2, + h3 { + scroll-margin: 5em; + } + } +} + +.docs-header { + margin-block-end: 1rem; + & > p:first-child { + color: var(--quaternary-contrast); + font-weight: 500; + margin: 0; + } +} + +.docs-page-title { + display: flex; + justify-content: space-between; + + h1 { + margin-block: 0; + font-size: 2.25rem; + } + + a { + color: var(--primary-contrast); + height: fit-content; + + docs-icon { + color: var(--gray-400); + transition: color 0.3s ease; + } + + &:hover { + docs-icon { + color: var(--primary-contrast); + } + } + } +} diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.spec.ts b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.spec.ts new file mode 100644 index 000000000000..578db765db37 --- /dev/null +++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.spec.ts @@ -0,0 +1,209 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {RouterTestingModule} from '@angular/router/testing'; +import {ExampleViewerContentLoader} from '../../../interfaces'; +import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers'; +import {CodeExampleViewMode, ExampleViewer} from '../example-viewer/example-viewer.component'; +import {DocViewer} from './docs-viewer.component'; +import {IconComponent} from '../../icon/icon.component'; +import {Breadcrumb} from '../../breadcrumb/breadcrumb.component'; +import {NavigationState} from '../../../services'; +import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; +import {TableOfContents} from '../../table-of-contents/table-of-contents.component'; +import {provideExperimentalZonelessChangeDetection} from '@angular/core'; + +describe('DocViewer', () => { + let fixture: ComponentFixture; + let exampleContentSpy: jasmine.SpyObj; + let navigationStateSpy: jasmine.SpyObj; + + const exampleDocContentWithExampleViewerPlaceholders = `
+
A styled code example
+
+      
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectorRef, Component, inject, signal} from '@angular/core';
import {Component, signal} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
selector: 'hello-world',
standalone: true,
imports: [CommonModule],
templateUrl: './hello-world.html',
styleUrls: ['./hello-world.css'],
})
export default class HelloWorldComponent {
world = 'World';
world = 'World!!!';
count = signal(0);
changeDetector = inject(ChangeDetectorRef);
increase(): void {
this.count.update((previous) => {
return previous + 1;
});
this.changeDetector.detectChanges();
}
}
+
+
`; + + const exampleDocContentWithExpandedExampleViewerPlaceholders = `
+
+
+      
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectorRef, Component, inject, signal} from '@angular/core';
import {Component, signal} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
selector: 'hello-world',
standalone: true,
imports: [CommonModule],
templateUrl: './hello-world.html',
styleUrls: ['./hello-world.css'],
})
export default class HelloWorldComponent {
world = 'World';
world = 'World!!!';
count = signal(0);
changeDetector = inject(ChangeDetectorRef);
increase(): void {
this.count.update((previous) => {
return previous + 1;
});
this.changeDetector.detectChanges();
}
}
+
+
+
+
+      
<h2>Hello {{ world }}</h2>
<button (click)="increase()">Increase</button>
<p>Counter: {{ count() }}</p>
+
+
+
`; + + const exampleContentWithIcons = ` +

Content

+ light_mode +

More content

+ dark_mode + `; + + const exampleContentWithBreadcrumbPlaceholder = ` + +

Content

+ `; + + const exampleContentWithCodeSnippet = ` +
+
+        
+          
+
+
+
+ `; + + const exampleContentWithHeadings = ` +

Heading h2

+

Heading h3

+ `; + + beforeEach(() => { + exampleContentSpy = jasmine.createSpyObj('ExampleViewerContentLoader', ['getCodeExampleData']); + navigationStateSpy = jasmine.createSpyObj(NavigationState, ['activeNavigationItem']); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DocViewer, NoopAnimationsModule, RouterTestingModule], + providers: [ + provideExperimentalZonelessChangeDetection(), + {provide: EXAMPLE_VIEWER_CONTENT_LOADER, useValue: exampleContentSpy}, + {provide: NavigationState, useValue: navigationStateSpy}, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DocViewer); + fixture.detectChanges(); + }); + + it('should load doc into innerHTML', () => { + const fixture = TestBed.createComponent(DocViewer); + fixture.componentRef.setInput('docContent', 'hello world'); + fixture.detectChanges(); + + expect(fixture.nativeElement.innerHTML).toBe('hello world'); + }); + + it('should instantiate example viewer in snippet view mode', async () => { + const fixture = TestBed.createComponent(DocViewer); + fixture.componentRef.setInput('docContent', exampleDocContentWithExampleViewerPlaceholders); + fixture.detectChanges(); + await fixture.whenStable(); + + const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer)); + + expect(exampleViewer).not.toBeNull(); + expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.SNIPPET); + }); + + it('should display example viewer in multi file mode when user clicks expand', async () => { + const fixture = TestBed.createComponent(DocViewer); + fixture.componentRef.setInput( + 'docContent', + exampleDocContentWithExpandedExampleViewerPlaceholders, + ); + fixture.detectChanges(); + await fixture.whenStable(); + + const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer)); + const expandButton = fixture.debugElement.query( + By.css('button[aria-label="Expand code example"]'), + ); + expandButton.nativeElement.click(); + + expect(exampleViewer).not.toBeNull(); + expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.MULTI_FILE); + expect(exampleViewer.componentInstance.tabs().length).toBe(2); + }); + + it('should render Icon component when content has element', async () => { + const fixture = TestBed.createComponent(DocViewer); + const renderComponentSpy = spyOn(fixture.componentInstance, 'renderComponent' as any); + fixture.componentRef.setInput('docContent', exampleContentWithIcons); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(renderComponentSpy).toHaveBeenCalledTimes(2); + expect(renderComponentSpy.calls.allArgs()[0][0]).toBe(IconComponent); + expect((renderComponentSpy.calls.allArgs()[0][1] as HTMLElement).innerText).toEqual( + `light_mode`, + ); + expect(renderComponentSpy.calls.allArgs()[1][0]).toBe(IconComponent); + expect((renderComponentSpy.calls.allArgs()[1][1] as HTMLElement).innerText).toEqual( + `dark_mode`, + ); + }); + + it('should render Breadcrumb component when content has element', async () => { + navigationStateSpy.activeNavigationItem.and.returnValue({ + label: 'Active Item', + parent: { + label: 'Parent Item', + }, + }); + + const fixture = TestBed.createComponent(DocViewer); + const renderComponentSpy = spyOn(fixture.componentInstance, 'renderComponent' as any); + fixture.componentRef.setInput('docContent', exampleContentWithBreadcrumbPlaceholder); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(renderComponentSpy).toHaveBeenCalledTimes(1); + expect(renderComponentSpy.calls.allArgs()[0][0]).toBe(Breadcrumb); + }); + + it('should render copy source code buttons', async () => { + const fixture = TestBed.createComponent(DocViewer); + fixture.componentRef.setInput('docContent', exampleContentWithCodeSnippet); + + fixture.detectChanges(); + await fixture.whenStable(); + + const copySourceCodeButton = fixture.debugElement.query(By.directive(CopySourceCodeButton)); + + expect(copySourceCodeButton).toBeTruthy(); + }); + + it('should render ToC', async () => { + const fixture = TestBed.createComponent(DocViewer); + const renderComponentSpy = spyOn(fixture.componentInstance, 'renderComponent' as any); + fixture.componentRef.setInput('docContent', exampleContentWithHeadings); + fixture.componentRef.setInput('hasToc', true); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(renderComponentSpy).toHaveBeenCalled(); + expect(renderComponentSpy.calls.allArgs()[0][0]).toBe(TableOfContents); + }); + + it('should not render ToC when hasToc is false', async () => { + const fixture = TestBed.createComponent(DocViewer); + const renderComponentSpy = spyOn(fixture.componentInstance, 'renderComponent' as any); + fixture.componentRef.setInput('docContent', exampleContentWithHeadings); + fixture.componentRef.setInput('hasToc', false); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(renderComponentSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts new file mode 100644 index 000000000000..c7f7a2dc3e3e --- /dev/null +++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts @@ -0,0 +1,349 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {CommonModule, DOCUMENT, isPlatformBrowser, Location} from '@angular/common'; +import { + ApplicationRef, + ChangeDetectionStrategy, + Component, + ComponentRef, + createComponent, + DestroyRef, + ElementRef, + EnvironmentInjector, + inject, + Injector, + Input, + OnChanges, + PLATFORM_ID, + SimpleChanges, + Type, + ViewContainerRef, + ViewEncapsulation, + ɵPendingTasks as PendingTasks, + EventEmitter, + Output, +} from '@angular/core'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {TOC_SKIP_CONTENT_MARKER, NavigationState} from '../../../services/index'; +import {TableOfContents} from '../../table-of-contents/table-of-contents.component'; +import {IconComponent} from '../../icon/icon.component'; +import {handleHrefClickEventWithRouter} from '../../../utils/index'; +import {Snippet} from '../../../interfaces/index'; +import {Router} from '@angular/router'; +import {fromEvent} from 'rxjs'; + +import {Breadcrumb} from '../../breadcrumb/breadcrumb.component'; +import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; +import {ExampleViewer} from '../example-viewer/example-viewer.component'; + +/// + +const TOC_HOST_ELEMENT_NAME = 'docs-table-of-contents'; +export const ASSETS_EXAMPLES_PATH = 'assets/content/examples'; +export const DOCS_VIEWER_SELECTOR = 'docs-viewer'; +export const DOCS_CODE_SELECTOR = '.docs-code'; +export const DOCS_CODE_MUTLIFILE_SELECTOR = '.docs-code-multifile'; +// TODO: Update the branch/sha +export const GITHUB_CONTENT_URL = + 'https://github.com/angular/angular/blob/main/adev/src/content/examples/'; + +@Component({ + selector: DOCS_VIEWER_SELECTOR, + standalone: true, + imports: [CommonModule], + template: '', + styleUrls: ['docs-viewer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { + '[class.docs-animate-content]': 'animateContent', + }, +}) +export class DocViewer implements OnChanges { + @Input() docContent?: string; + @Input() hasToc = false; + @Output() contentLoaded = new EventEmitter(); + + private readonly destroyRef = inject(DestroyRef); + private readonly document = inject(DOCUMENT); + private readonly elementRef = inject(ElementRef); + private readonly location = inject(Location); + private readonly navigationState = inject(NavigationState); + private readonly platformId = inject(PLATFORM_ID); + private readonly router = inject(Router); + private readonly viewContainer = inject(ViewContainerRef); + private readonly environmentInjector = inject(EnvironmentInjector); + private readonly injector = inject(Injector); + private readonly appRef = inject(ApplicationRef); + + // tslint:disable-next-line:no-unused-variable + private animateContent = false; + private readonly pendingRenderTasks = inject(PendingTasks); + + private countOfExamples = 0; + + async ngOnChanges(changes: SimpleChanges): Promise { + const taskId = this.pendingRenderTasks.add(); + if ('docContent' in changes) { + await this.renderContentsAndRunClientSetup(this.docContent!); + } + this.pendingRenderTasks.remove(taskId); + } + + async renderContentsAndRunClientSetup(content?: string): Promise { + const isBrowser = isPlatformBrowser(this.platformId); + const contentContainer = this.elementRef.nativeElement; + + if (content) { + if (isBrowser && !(this.document as any).startViewTransition) { + // Apply a special class to the host node to trigger animation. + // Note: when a page is hydrated, the `content` would be empty, + // so we don't trigger an animation to avoid a content flickering + // visual effect. In addition, if the browser supports view transitions (startViewTransition is present), the animation is handled by the native View Transition API so it does not need to be done here. + this.animateContent = true; + } + + contentContainer.innerHTML = content; + } + + if (isBrowser) { + // First we setup event listeners on the HTML we just loaded. + // We want to do this before things like the example viewers are loaded. + this.setupAnchorListeners(contentContainer); + // Rewrite relative anchors (hrefs starting with `#`) because relative hrefs are relative to the base URL, which is '/' + this.rewriteRelativeAnchors(contentContainer); + // In case when content contains placeholders for executable examples, create ExampleViewer components. + await this.loadExamples(); + // In case when content contains static code snippets, then create buttons + // responsible for copy source code. + this.loadCopySourceCodeButtons(); + } + + // Display Breadcrumb component if the `` element exists + this.loadBreadcrumb(contentContainer); + + // Display Icon component if the `` element exists + this.loadIcons(contentContainer); + + // Render ToC + this.renderTableOfContents(contentContainer); + + this.contentLoaded.next(); + } + + /** + * Load ExampleViewer component when: + * - exists docs-code-multifile element with multiple files OR + * - exists docs-code element with single file AND + * - 'preview' attribute was provided OR + * - 'visibleLines' attribute was provided + */ + private async loadExamples(): Promise { + const multifileCodeExamples = ( + Array.from(this.elementRef.nativeElement.querySelectorAll(DOCS_CODE_MUTLIFILE_SELECTOR)) + ); + + for (let placeholder of multifileCodeExamples) { + const path = placeholder.getAttribute('path')!; + const snippets = this.getCodeSnippetsFromMultifileWrapper(placeholder); + await this.renderExampleViewerComponents(placeholder, snippets, path); + } + + const docsCodeElements = this.elementRef.nativeElement.querySelectorAll(DOCS_CODE_SELECTOR); + + for (const placeholder of docsCodeElements) { + const snippet = this.getStandaloneCodeSnippet(placeholder); + if (snippet) { + await this.renderExampleViewerComponents(placeholder, [snippet], snippet.name); + } + } + } + + private renderTableOfContents(element: HTMLElement): void { + if (!this.hasToc) { + return; + } + + const firstHeading = element.querySelector('h2,h3[id]'); + if (!firstHeading) { + return; + } + + // Since the content of the main area is dynamically created and there is + // no host element for a ToC component, we create it manually. + let tocHostElement: HTMLElement | null = element.querySelector(TOC_HOST_ELEMENT_NAME); + if (!tocHostElement) { + tocHostElement = this.document.createElement(TOC_HOST_ELEMENT_NAME); + tocHostElement.setAttribute(TOC_SKIP_CONTENT_MARKER, 'true'); + firstHeading?.parentNode?.insertBefore(tocHostElement, firstHeading); + } + + this.renderComponent(TableOfContents, tocHostElement, {contentSourceElement: element}); + } + + private async renderExampleViewerComponents( + placeholder: HTMLElement, + snippets: Snippet[], + path: string, + ): Promise { + const preview = Boolean(placeholder.getAttribute('preview')); + const title = placeholder.getAttribute('header') ?? undefined; + const firstCodeSnippetTitle = + snippets.length > 0 ? snippets[0].title ?? snippets[0].name : undefined; + const exampleRef = this.viewContainer.createComponent(ExampleViewer); + + this.countOfExamples++; + exampleRef.instance.metadata = { + title: title ?? firstCodeSnippetTitle, + path, + files: snippets, + preview, + id: this.countOfExamples, + }; + + exampleRef.instance.githubUrl = `${GITHUB_CONTENT_URL}/${snippets[0].name}`; + exampleRef.instance.stackblitzUrl = `${ASSETS_EXAMPLES_PATH}/${snippets[0].name}.html`; + + placeholder.parentElement!.replaceChild(exampleRef.location.nativeElement, placeholder); + + await exampleRef.instance.renderExample(); + } + + private getCodeSnippetsFromMultifileWrapper(element: HTMLElement): Snippet[] { + const tabs = Array.from(element.querySelectorAll(DOCS_CODE_SELECTOR)); + + return tabs.map((tab) => ({ + name: tab.getAttribute('path') ?? tab.getAttribute('header') ?? '', + content: tab.innerHTML, + visibleLinesRange: tab.getAttribute('visibleLines') ?? undefined, + })); + } + + private getStandaloneCodeSnippet(element: HTMLElement): Snippet | null { + const visibleLines = element.getAttribute('visibleLines') ?? undefined; + const preview = element.getAttribute('preview'); + + if (!visibleLines && !preview) { + return null; + } + + const content = element.querySelector('pre')!; + const path = element.getAttribute('path')!; + const title = element.getAttribute('header') ?? undefined; + + return { + title, + name: path, + content: content?.outerHTML, + visibleLinesRange: visibleLines, + }; + } + + // If the content contains static code snippets, we should add buttons to copy + // the code + private loadCopySourceCodeButtons(): void { + const staticCodeSnippets = ( + Array.from(this.elementRef.nativeElement.querySelectorAll('.docs-code:not([mermaid])')) + ); + + for (let codeSnippet of staticCodeSnippets) { + const copySourceCodeButton = this.viewContainer.createComponent(CopySourceCodeButton); + codeSnippet.appendChild(copySourceCodeButton.location.nativeElement); + } + } + + private loadBreadcrumb(element: HTMLElement): void { + const breadcrumbPlaceholder = element.querySelector('docs-breadcrumb') as HTMLElement; + const activeNavigationItem = this.navigationState.activeNavigationItem(); + + if (breadcrumbPlaceholder && !!activeNavigationItem?.parent) { + this.renderComponent(Breadcrumb, breadcrumbPlaceholder); + } + } + + private loadIcons(element: HTMLElement): void { + element.querySelectorAll('docs-icon').forEach((iconsPlaceholder) => { + this.renderComponent(IconComponent, iconsPlaceholder as HTMLElement); + }); + } + + /** + * Helper method to render a component dynamically in a context of this class. + */ + private renderComponent( + type: Type, + hostElement: HTMLElement, + inputs?: {[key: string]: unknown}, + ): ComponentRef { + const componentRef = createComponent(type, { + hostElement, + elementInjector: this.injector, + environmentInjector: this.environmentInjector, + }); + + if (inputs) { + for (const [name, value] of Object.entries(inputs)) { + componentRef.setInput(name, value); + } + } + + // Trigger change detection after setting inputs. + componentRef.changeDetectorRef.detectChanges(); + + // Attach a view to the ApplicationRef for change detection + // purposes and for hydration serialization to pick it up + // during SSG. + this.appRef.attachView(componentRef.hostView); + + return componentRef; + } + + private setupAnchorListeners(element: HTMLElement): void { + element.querySelectorAll(`a[href]`).forEach((anchor) => { + // Get the target element's ID from the href attribute + const url = new URL((anchor as HTMLAnchorElement).href); + const isExternalLink = url.origin !== this.document.location.origin; + if (isExternalLink) { + return; + } + + fromEvent(anchor, 'click') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((e) => { + const closestAnchor = (e.target as Element).closest('a'); + if (closestAnchor?.target && closestAnchor.target !== 'self') { + return; + } + + const hrefAttr = closestAnchor?.getAttribute?.('href'); + if (!hrefAttr) { + return; + } + + let relativeUrl: string; + if (hrefAttr.startsWith('http')) { + // Url is absolute but we're targeting the same domain + const url = new URL(hrefAttr); + relativeUrl = `${url.pathname}${url.hash}${url.search}`; + } else { + relativeUrl = hrefAttr; + } + + handleHrefClickEventWithRouter(e, this.router, relativeUrl); + }); + }); + } + + private rewriteRelativeAnchors(element: HTMLElement) { + for (const anchor of Array.from(element.querySelectorAll(`a[href^="#"]:not(a[download])`))) { + const url = new URL((anchor as HTMLAnchorElement).href); + (anchor as HTMLAnchorElement).href = this.location.path() + url.hash; + } + } +} diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html new file mode 100644 index 000000000000..be364bf503e2 --- /dev/null +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html @@ -0,0 +1,155 @@ +
+
+ @if (view() === CodeExampleViewMode.SNIPPET) { + {{ exampleMetadata()?.title }} + } + + @if (view() === CodeExampleViewMode.MULTI_FILE) { + + @for (tab of tabs(); track tab) { + + } + + } + +
+ + + @if (expandable()) { + + } +
+
+ +
+ + +
+ + @if (exampleComponent) { +
+ +
+ } + + + @if (exampleComponent) { + @if (githubUrl) { + + + + } + @if (stackblitzUrl) { + + + + } + } + +
diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss new file mode 100644 index 000000000000..4d572e96c4ad --- /dev/null +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss @@ -0,0 +1,129 @@ +:host { + .docs-example-viewer-preview { + .docs-dark-mode & { + background: var(--gray-100); + } + @media screen and (prefers-color-scheme: dark) { + background: var(--gray-100); + } + .docs-light-mode & { + background: var(--page-background); + } + } +} + +.docs-example-viewer { + border: 1px solid var(--senary-contrast); + border-radius: 0.25rem; + overflow: hidden; +} + +// Example viewer header +.docs-example-viewer-actions { + background: var(--subtle-purple); + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; + border-bottom: 1px solid var(--senary-contrast); + transition: background 0.3s ease, border-color 0.3s ease; + padding-inline-end: 0.65rem; + font-family: var(--inter-tight-font); + + mat-tab-group { + max-width: calc(100% - 140px); + } + + span:first-of-type { + background-image: var(--purple-to-blue-horizontal-gradient); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + + padding: 0.7rem 1.1rem; + font-size: 0.875rem; + font-style: normal; + font-weight: 400; + line-height: 1.4rem; + letter-spacing: -0.00875rem; + margin: 0; + word-wrap: break-word; + width: fit-content; + } + + .docs-example-viewer-icons { + display: flex; + gap: 0.75rem; + + svg { + fill: var(--gray-400); + } + } + + a, + button { + padding: 0; + margin: 0; + cursor: pointer; + height: 24px; + width: 24px; + path { + transition: fill 0.3s ease; + } + + &:hover { + svg { + fill: var(--tertiary-contrast); + } + } + } +} + +// Example viewer code +.docs-example-viewer-code-wrapper { + position: relative; + font-size: 0.875rem; + // TODO: only show this if there is a preview + // border-block-end: 1px solid var(--senary-contrast); + transition: border-color 0.3s ease; + container: viewerblock / inline-size; + background-color: var(--octonary-contrast); + + button[docs-copy-source-code] { + top: 0.31rem; + } +} + +// stylelint-disable-next-line +::ng-deep { + .docs-example-viewer-preview { + // stylelint-disable-next-line + all: initial; + display: block; + padding: 1rem; + border-block-start: 1px solid var(--senary-contrast); + + *, + code::before, + code, + pre, + a, + i, + p, + h1, + h2, + h3, + h4, + h5, + h6, + ol, + ul, + li, + hr, + input, + select, + table { + all: revert; + } + } +} diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts new file mode 100644 index 000000000000..d0656feb829d --- /dev/null +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts @@ -0,0 +1,230 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; +import {ExampleViewer} from './example-viewer.component'; +import {DocsContentLoader, ExampleMetadata, ExampleViewerContentLoader} from '../../../interfaces'; +import {DOCS_CONTENT_LOADER, EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers'; +import {Component, provideExperimentalZonelessChangeDetection} from '@angular/core'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {Clipboard} from '@angular/cdk/clipboard'; +import {By} from '@angular/platform-browser'; +import {MatTabGroupHarness} from '@angular/material/tabs/testing'; +import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; +import {ActivatedRoute} from '@angular/router'; + +describe('ExampleViewer', () => { + let component: ExampleViewer; + let fixture: ComponentFixture; + let loader: HarnessLoader; + let exampleContentSpy: jasmine.SpyObj; + let contentServiceSpy: jasmine.SpyObj; + + beforeEach(() => { + exampleContentSpy = jasmine.createSpyObj('ExampleContentLoader', ['loadPreview']); + contentServiceSpy = jasmine.createSpyObj('ContentLoader', ['getContent']); + contentServiceSpy.getContent.and.returnValue(Promise.resolve(undefined)); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ExampleViewer, NoopAnimationsModule], + providers: [ + provideExperimentalZonelessChangeDetection(), + {provide: EXAMPLE_VIEWER_CONTENT_LOADER, useValue: exampleContentSpy}, + {provide: DOCS_CONTENT_LOADER, useValue: contentServiceSpy}, + {provide: ActivatedRoute, useValue: {snapshot: {fragment: 'fragment'}}}, + ], + }).compileComponents(); + fixture = TestBed.createComponent(ExampleViewer); + component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + fixture.detectChanges(); + }); + + it('should set file extensions as tab names when all files have different extension', waitForAsync(async () => { + component.metadata = getMetadata({ + files: [ + {name: 'file.ts', content: ''}, + {name: 'file.html', content: ''}, + {name: 'file.css', content: ''}, + ], + }); + + await component.renderExample(); + + expect(component.tabs()!.length).toBe(3); + expect(component.tabs()![0].name).toBe('TS'); + expect(component.tabs()![1].name).toBe('HTML'); + expect(component.tabs()![2].name).toBe('CSS'); + })); + + it('should generate correct code content for multi file mode when it is expanded', waitForAsync(async () => { + component.metadata = getMetadata({ + files: [ + {name: 'file.ts', content: 'typescript file'}, + {name: 'file.html', content: 'html file'}, + {name: 'file.css', content: 'css file'}, + ], + }); + + await component.renderExample(); + + expect(component.tabs()!.length).toBe(3); + expect(component.tabs()![0].code).toBe('typescript file'); + expect(component.tabs()![1].code).toBe('html file'); + expect(component.tabs()![2].code).toBe('css file'); + })); + + it('should set file names as tab names when there is at least one duplication', async () => { + component.metadata = getMetadata({ + files: [ + {name: 'example.ts', content: 'typescript file'}, + {name: 'example.html', content: 'html file'}, + {name: 'another-example.ts', content: 'css file'}, + ], + }); + + await component.renderExample(); + expect(component.tabs()!.length).toBe(3); + expect(component.tabs()![0].name).toBe('example.ts'); + expect(component.tabs()![1].name).toBe('example.html'); + expect(component.tabs()![2].name).toBe('another-example.ts'); + }); + + it('should expandable be false when none of the example files have defined visibleLinesRange ', waitForAsync(async () => { + component.metadata = getMetadata(); + await component.renderExample(); + expect(component.expandable()).toBeFalse(); + })); + + it('should expandable be true when at least one example file has defined visibleLinesRange ', waitForAsync(async () => { + component.metadata = getMetadata({ + files: [ + {name: 'example.ts', content: 'typescript file'}, + { + name: 'example.html', + content: 'html file', + visibleLinesRange: '[1, 2]', + }, + {name: 'another-example.ts', content: 'css file'}, + ], + }); + await component.renderExample(); + expect(component.expandable()).toBeTrue(); + })); + + it('should set exampleComponent when metadata contains path and preview is true', waitForAsync(async () => { + exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent); + component.metadata = getMetadata({ + path: 'example.ts', + preview: true, + }); + await component.renderExample(); + expect(component.exampleComponent).toBe(ExampleComponent); + })); + + it('should display GitHub button when githubUrl is provided and there is preview', waitForAsync(async () => { + exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent); + component.metadata = getMetadata({ + path: 'example.ts', + preview: true, + }); + component.githubUrl = 'https://github.com/'; + await component.renderExample(); + const githubButton = fixture.debugElement.query( + By.css('a[aria-label="Open example on GitHub"]'), + ); + expect(githubButton).toBeTruthy(); + expect(githubButton.nativeElement.href).toBe(component.githubUrl); + })); + + it('should display StackBlitz button when stackblitzUrl is provided and there is preview', waitForAsync(async () => { + exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent); + component.metadata = getMetadata({ + path: 'example.ts', + preview: true, + }); + component.stackblitzUrl = 'https://stackblitz.com/'; + await component.renderExample(); + const stackblitzButton = fixture.debugElement.query( + By.css('a[aria-label="Edit this example in StackBlitz"]'), + ); + expect(stackblitzButton).toBeTruthy(); + expect(stackblitzButton.nativeElement.href).toBe(component.stackblitzUrl); + })); + + it('should set expanded flag in metadata after toggleExampleVisibility', waitForAsync(async () => { + component.metadata = getMetadata(); + await component.renderExample(); + component.toggleExampleVisibility(); + expect(component.expanded()).toBeTrue(); + const tabGroup = await loader.getHarness(MatTabGroupHarness); + const tab = await tabGroup.getSelectedTab(); + expect(await tab.getLabel()).toBe('TS'); + component.toggleExampleVisibility(); + expect(component.expanded()).toBeFalse(); + })); + + // TODO(josephperrott): enable once the docs-viewer/example-viewer circle is sorted out. + xit('should call clipboard service when clicked on copy source code', waitForAsync(async () => { + const expectedCodeSnippetContent = 'typescript code'; + component.metadata = getMetadata({ + files: [ + { + name: 'example.ts', + content: `
${expectedCodeSnippetContent}
`, + }, + {name: 'example.css', content: ''}, + ], + }); + const clipboardService = TestBed.inject(Clipboard); + const spy = spyOn(clipboardService, 'copy'); + + await component.renderExample(); + const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement; + button.click(); + + expect(spy.calls.argsFor(0)[0]?.trim()).toBe(expectedCodeSnippetContent); + })); + + it('should call clipboard service when clicked on copy example link', waitForAsync(async () => { + component.metadata = getMetadata(); + component.expanded.set(true); + fixture.detectChanges(); + + const clipboardService = TestBed.inject(Clipboard); + const spy = spyOn(clipboardService, 'copy'); + await component.renderExample(); + const button = fixture.debugElement.query( + By.css('button.docs-example-copy-link'), + ).nativeElement; + button.click(); + expect(spy.calls.argsFor(0)[0].trim()).toBe(`${window.origin}/context.html#example-1`); + })); +}); + +const getMetadata = (value: Partial = {}): ExampleMetadata => { + return { + id: 1, + files: [ + {name: 'example.ts', content: ''}, + {name: 'example.css', content: ''}, + ], + preview: false, + ...value, + }; +}; + +@Component({ + template: '', + standalone: true, +}) +class ExampleComponent {} diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts new file mode 100644 index 000000000000..0055363b1e48 --- /dev/null +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts @@ -0,0 +1,243 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + Input, + Type, + computed, + inject, + ChangeDetectorRef, + ViewChild, + signal, + ElementRef, + forwardRef, +} from '@angular/core'; +import {CommonModule, DOCUMENT} from '@angular/common'; +import {MatTabGroup, MatTabsModule} from '@angular/material/tabs'; +import {Clipboard} from '@angular/cdk/clipboard'; +import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; +import {ExampleMetadata, Snippet} from '../../../interfaces/index'; +import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {DocViewer} from '../docs-viewer/docs-viewer.component'; + +export enum CodeExampleViewMode { + SNIPPET = 'snippet', + MULTI_FILE = 'multi', +} + +export const CODE_LINE_NUMBER_CLASS_NAME = 'shiki-ln-number'; +export const CODE_LINE_CLASS_NAME = 'line'; +export const GAP_CODE_LINE_CLASS_NAME = 'gap'; +export const HIDDEN_CLASS_NAME = 'hidden'; + +@Component({ + selector: 'docs-example-viewer', + standalone: true, + imports: [CommonModule, forwardRef(() => DocViewer), CopySourceCodeButton, MatTabsModule], + templateUrl: './example-viewer.component.html', + styleUrls: ['./example-viewer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ExampleViewer { + // TODO: replace by signal-based input when it'll be available + @Input({required: true}) set metadata(value: ExampleMetadata) { + this.exampleMetadata.set(value); + } + + @Input() githubUrl: string | null = null; + @Input() stackblitzUrl: string | null = null; + @ViewChild('codeTabs') matTabGroup?: MatTabGroup; + + private readonly changeDetector = inject(ChangeDetectorRef); + private readonly clipboard = inject(Clipboard); + private readonly destroyRef = inject(DestroyRef); + private readonly document = inject(DOCUMENT); + private readonly elementRef = inject(ElementRef); + private readonly exampleViewerContentLoader = inject(EXAMPLE_VIEWER_CONTENT_LOADER); + + private readonly shouldDisplayFullName = computed(() => { + const fileExtensions = + this.exampleMetadata()?.files.map((file) => this.getFileExtension(file.name)) ?? []; + + // Display full file names only when exist files with the same extension + return new Set(fileExtensions).size !== fileExtensions.length; + }); + + CodeExampleViewMode = CodeExampleViewMode; + exampleComponent?: Type; + + expanded = signal(false); + exampleMetadata = signal(null); + snippetCode = signal(undefined); + tabs = computed(() => + this.exampleMetadata()?.files.map((file) => ({ + name: + file.title ?? (this.shouldDisplayFullName() ? file.name : this.getFileExtension(file.name)), + code: file.content, + })), + ); + view = computed(() => + this.exampleMetadata()?.files.length === 1 + ? CodeExampleViewMode.SNIPPET + : CodeExampleViewMode.MULTI_FILE, + ); + expandable = computed(() => + this.exampleMetadata()?.files.some((file) => !!file.visibleLinesRange), + ); + + async renderExample(): Promise { + // Lazy load live example component + if (this.exampleMetadata()?.path && this.exampleMetadata()?.preview) { + this.exampleComponent = await this.exampleViewerContentLoader.loadPreview( + this.exampleMetadata()?.path!, + ); + } + + this.snippetCode.set(this.exampleMetadata()?.files[0]); + + this.changeDetector.detectChanges(); + + this.setCodeLinesVisibility(); + + this.elementRef.nativeElement.setAttribute( + 'id', + `example-${this.exampleMetadata()?.id.toString()!}`, + ); + + this.matTabGroup?.realignInkBar(); + + this.listenToMatTabIndexChange(); + } + + toggleExampleVisibility(): void { + this.expanded.update((expanded) => !expanded); + + this.setCodeLinesVisibility(); + } + + copyLink(): void { + // Reconstruct the URL using `origin + pathname` so we drop any pre-existing hash. + const fullUrl = + location.origin + + location.pathname + + location.search + + '#example-' + + this.exampleMetadata()?.id; + this.clipboard.copy(fullUrl); + } + + private listenToMatTabIndexChange(): void { + this.matTabGroup?.realignInkBar(); + this.matTabGroup?.selectedIndexChange + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((index) => { + this.snippetCode.set(this.exampleMetadata()?.files[index]); + this.changeDetector.detectChanges(); + this.setCodeLinesVisibility(); + }); + } + + private getFileExtension(name: string): string { + const segments = name.split('.'); + return segments.length ? segments[segments.length - 1].toLocaleUpperCase() : ''; + } + + private setCodeLinesVisibility(): void { + this.expanded() + ? this.handleExpandedStateForCodeBlock() + : this.handleCollapsedStateForCodeBlock(); + } + + private handleExpandedStateForCodeBlock(): void { + const lines = ( + Array.from( + this.elementRef.nativeElement.querySelectorAll( + `.${CODE_LINE_CLASS_NAME}.${HIDDEN_CLASS_NAME}`, + ), + ) + ); + + const lineNumbers = ( + Array.from( + this.elementRef.nativeElement.querySelectorAll( + `.${CODE_LINE_NUMBER_CLASS_NAME}.${HIDDEN_CLASS_NAME}`, + ), + ) + ); + + const gapLines = ( + Array.from( + this.elementRef.nativeElement.querySelectorAll( + `.${CODE_LINE_CLASS_NAME}.${GAP_CODE_LINE_CLASS_NAME}`, + ), + ) + ); + + for (const line of lines) { + line.classList.remove(HIDDEN_CLASS_NAME); + } + + for (const lineNumber of lineNumbers) { + lineNumber.classList.remove(HIDDEN_CLASS_NAME); + } + + for (const expandLine of gapLines) { + expandLine.remove(); + } + } + + private handleCollapsedStateForCodeBlock(): void { + const visibleLinesRange = this.snippetCode()?.visibleLinesRange; + + if (!visibleLinesRange) { + return; + } + + const linesToDisplay = (visibleLinesRange?.split(',') ?? []).map((line) => Number(line)); + const lines = ( + Array.from(this.elementRef.nativeElement.querySelectorAll(`.${CODE_LINE_CLASS_NAME}`)) + ); + const lineNumbers = ( + Array.from(this.elementRef.nativeElement.querySelectorAll(`.${CODE_LINE_NUMBER_CLASS_NAME}`)) + ); + const appendGapBefore = []; + + for (const [index, line] of lines.entries()) { + if (!linesToDisplay.includes(index)) { + line.classList.add(HIDDEN_CLASS_NAME); + } else if (!linesToDisplay.includes(index - 1)) { + appendGapBefore.push(line); + } + } + + for (const [index, lineNumber] of lineNumbers.entries()) { + if (!linesToDisplay.includes(index)) { + lineNumber.classList.add(HIDDEN_CLASS_NAME); + } + } + + // Create gap line between visible ranges. For example we would like to display 10-16 and 20-29 lines. + // We should display separator, gap between those two scopes. + // TODO: we could replace div it with the component, and allow to expand code block after click. + for (const [index, element] of appendGapBefore.entries()) { + if (index === 0) { + continue; + } + + const separator = this.document.createElement('div'); + separator.textContent = `...`; + separator.classList.add(CODE_LINE_CLASS_NAME); + separator.classList.add(GAP_CODE_LINE_CLASS_NAME); + element.parentNode?.insertBefore(separator, element); + } + } +} diff --git a/adev/shared-docs/constants/BUILD.bazel b/adev/shared-docs/constants/BUILD.bazel new file mode 100644 index 000000000000..5a33d97a8704 --- /dev/null +++ b/adev/shared-docs/constants/BUILD.bazel @@ -0,0 +1,28 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "constants", + srcs = [ + "index.ts", + ], + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + ":lib", + ], +) + +ts_library( + name = "lib", + srcs = glob( + [ + "*.ts", + ], + exclude = [ + "index.ts", + ], + ), +) diff --git a/adev/shared-docs/constants/delay.ts b/adev/shared-docs/constants/delay.ts new file mode 100644 index 000000000000..fd62285861c6 --- /dev/null +++ b/adev/shared-docs/constants/delay.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// Used for both the table of contents and the home animation +export const RESIZE_EVENT_DELAY = 500; + +// Used for the home animation +export const WEBGL_LOADED_DELAY = 250; diff --git a/adev/shared-docs/constants/index.ts b/adev/shared-docs/constants/index.ts new file mode 100644 index 000000000000..092d17d72e11 --- /dev/null +++ b/adev/shared-docs/constants/index.ts @@ -0,0 +1,9 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './delay'; diff --git a/adev/shared-docs/directives/BUILD.bazel b/adev/shared-docs/directives/BUILD.bazel new file mode 100644 index 000000000000..759ea98aab8f --- /dev/null +++ b/adev/shared-docs/directives/BUILD.bazel @@ -0,0 +1,18 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "directives", + srcs = [ + "index.ts", + ], + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + "//adev/shared-docs/directives/click-outside", + "//adev/shared-docs/directives/external-link", + "//adev/shared-docs/directives/search-item", + ], +) diff --git a/adev/shared-docs/directives/click-outside/BUILD.bazel b/adev/shared-docs/directives/click-outside/BUILD.bazel new file mode 100644 index 000000000000..32bed5e4047e --- /dev/null +++ b/adev/shared-docs/directives/click-outside/BUILD.bazel @@ -0,0 +1,38 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "click-outside", + srcs = [ + "click-outside.directive.ts", + ], + visibility = [ + "//adev/shared-docs/directives:__pkg__", + ], + deps = [ + "//packages/common", + "//packages/core", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":click-outside", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "//packages/router", + "//packages/router/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/directives/click-outside/click-outside.directive.spec.ts b/adev/shared-docs/directives/click-outside/click-outside.directive.spec.ts new file mode 100644 index 000000000000..297731381394 --- /dev/null +++ b/adev/shared-docs/directives/click-outside/click-outside.directive.spec.ts @@ -0,0 +1,77 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Component} from '@angular/core'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {ClickOutside} from './click-outside.directive'; +import {By} from '@angular/platform-browser'; + +describe('ClickOutside', () => { + let component: ExampleComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ExampleComponent, RouterTestingModule], + }); + fixture = TestBed.createComponent(ExampleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should docsClickOutside be emitted when user click outside `content` element', () => { + const clickedOutsideSpy = spyOn(component, 'clickedOutside'); + const button = fixture.debugElement.query(By.css('button[id="exampleButton"]')); + + button.nativeElement.click(); + + expect(clickedOutsideSpy).toHaveBeenCalledTimes(1); + }); + + it('should not docsClickOutside be emitted when user click inside `content` element', () => { + const clickedOutsideSpy = spyOn(component, 'clickedOutside'); + const content = fixture.debugElement.query(By.css('div[id="content"]')); + + content.nativeElement.click(); + + expect(clickedOutsideSpy).not.toHaveBeenCalled(); + }); + + it('should not docsClickOutside be emitted when user click inside `content` element', () => { + const clickedOutsideSpy = spyOn(component, 'clickedOutside'); + const button = fixture.debugElement.query(By.css('button[id="ignoreThisButton"]')); + + button.nativeElement.click(); + + expect(clickedOutsideSpy).not.toHaveBeenCalled(); + }); +}); + +@Component({ + template: ` +
+ + +
+ Content +
+
+ `, + imports: [ClickOutside], + standalone: true, +}) +class ExampleComponent { + docsClickOutsideIgnore = ['ignoreThisButton']; + + clickedOutside(): void {} +} diff --git a/adev/shared-docs/directives/click-outside/click-outside.directive.ts b/adev/shared-docs/directives/click-outside/click-outside.directive.ts new file mode 100644 index 000000000000..11ab10a738aa --- /dev/null +++ b/adev/shared-docs/directives/click-outside/click-outside.directive.ts @@ -0,0 +1,47 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {DOCUMENT} from '@angular/common'; +import {Directive, ElementRef, EventEmitter, Input, Output, inject} from '@angular/core'; + +@Directive({ + selector: '[docsClickOutside]', + standalone: true, + host: { + '(document:click)': 'onClick($event)', + }, +}) +export class ClickOutside { + @Input('docsClickOutsideIgnore') public ignoredElementsIds: string[] = []; + @Output('docsClickOutside') public clickOutside = new EventEmitter(); + + private readonly document = inject(DOCUMENT); + private readonly elementRef = inject(ElementRef); + + onClick($event: PointerEvent): void { + if ( + !this.elementRef.nativeElement.contains($event.target) && + !this.wasClickedOnIgnoredElement($event) + ) { + this.clickOutside.emit(); + } + } + + private wasClickedOnIgnoredElement($event: PointerEvent): boolean { + if (this.ignoredElementsIds.length === 0) { + return false; + } + + return this.ignoredElementsIds.some((elementId) => { + const element = this.document.getElementById(elementId); + const target = $event.target as Node; + const contains = element?.contains(target); + return contains; + }); + } +} diff --git a/adev/shared-docs/directives/external-link/BUILD.bazel b/adev/shared-docs/directives/external-link/BUILD.bazel new file mode 100644 index 000000000000..f95035631f91 --- /dev/null +++ b/adev/shared-docs/directives/external-link/BUILD.bazel @@ -0,0 +1,42 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "external-link", + srcs = [ + "external-link.directive.ts", + ], + visibility = [ + "//adev/shared-docs/directives:__pkg__", + ], + deps = [ + "//adev/shared-docs/providers", + "//adev/shared-docs/utils", + "//packages/common", + "//packages/core", + "//packages/router", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":external-link", + "//adev/shared-docs/providers", + "//packages/core", + "//packages/core/testing", + "//packages/platform-browser", + "//packages/router", + "//packages/router/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/directives/external-link/external-link.directive.spec.ts b/adev/shared-docs/directives/external-link/external-link.directive.spec.ts new file mode 100644 index 000000000000..b0c9412ae47b --- /dev/null +++ b/adev/shared-docs/directives/external-link/external-link.directive.spec.ts @@ -0,0 +1,77 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Component} from '@angular/core'; +import {ExternalLink} from './external-link.directive'; +import {RouterLink} from '@angular/router'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {WINDOW} from '../../providers'; +import {RouterTestingModule} from '@angular/router/testing'; +import {By} from '@angular/platform-browser'; + +describe('ExternalLink', () => { + let fixture: ComponentFixture; + const fakeWindow = { + location: { + origin: window.origin, + }, + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ExampleComponentWithLinks, RouterTestingModule], + providers: [ + { + provide: WINDOW, + useValue: fakeWindow, + }, + ], + }); + fixture = TestBed.createComponent(ExampleComponentWithLinks); + fixture.detectChanges(); + }); + + it('should external link have target=_blank attribute', () => { + const externalLink = fixture.debugElement.query( + By.css('a[href="https://stackoverflow.com/questions/tagged/angular"]'), + ); + expect(externalLink.attributes['target']).toEqual('_blank'); + }); + + it('should not internal link have target=_blank attribute', () => { + const internalLink = fixture.debugElement.query(By.css('a[href="/roadmap"]')); + expect(internalLink.attributes['target']).toBeFalsy(); + }); + + it('should not set target=_blank attribute external link when anchor has got noBlankForExternalLink attribute', () => { + const externalLink = fixture.debugElement.query( + By.css('a[href="https://github.com/angular/angular/issues"]'), + ); + expect(externalLink.attributes['target']).toBeFalsy(); + }); +}); + +@Component({ + template: ` + + Stack Overflow + + Roadmap + + `, + imports: [ExternalLink, RouterLink], + standalone: true, +}) +class ExampleComponentWithLinks {} diff --git a/adev/shared-docs/directives/external-link/external-link.directive.ts b/adev/shared-docs/directives/external-link/external-link.directive.ts new file mode 100644 index 000000000000..8f2cda063d50 --- /dev/null +++ b/adev/shared-docs/directives/external-link/external-link.directive.ts @@ -0,0 +1,45 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {isPlatformBrowser} from '@angular/common'; +import {Directive, ElementRef, OnInit, PLATFORM_ID, inject} from '@angular/core'; +import {isExternalLink} from '../../utils/index'; +import {WINDOW} from '../../providers/index'; + +/** + * The directive will set target of anchor elements to '_blank' for the external links. + * We can opt-out this behavior by adding `noBlankForExternalLink` attribute to anchor element. + */ +@Directive({ + selector: 'a[href]:not([noBlankForExternalLink])', + host: { + '[attr.target]': 'target', + }, + standalone: true, +}) +export class ExternalLink implements OnInit { + private readonly anchor: ElementRef = inject(ElementRef); + private readonly platformId = inject(PLATFORM_ID); + private readonly window = inject(WINDOW); + + target?: '_blank' | '_self' | '_parent' | '_top' | ''; + + ngOnInit(): void { + this.setAnchorTarget(); + } + + private setAnchorTarget(): void { + if (!isPlatformBrowser(this.platformId)) { + return; + } + + if (isExternalLink(this.anchor.nativeElement.href, this.window.location.origin)) { + this.target = '_blank'; + } + } +} diff --git a/adev/shared-docs/directives/index.ts b/adev/shared-docs/directives/index.ts new file mode 100644 index 000000000000..1f636703bca2 --- /dev/null +++ b/adev/shared-docs/directives/index.ts @@ -0,0 +1,10 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './click-outside/click-outside.directive'; +export * from './external-link/external-link.directive'; diff --git a/adev/shared-docs/directives/search-item/BUILD.bazel b/adev/shared-docs/directives/search-item/BUILD.bazel new file mode 100644 index 000000000000..7ac10274e5c5 --- /dev/null +++ b/adev/shared-docs/directives/search-item/BUILD.bazel @@ -0,0 +1,18 @@ +load("//tools:defaults.bzl", "ng_module") + +package(default_visibility = ["//visibility:private"]) + +ng_module( + name = "search-item", + srcs = [ + "search-item.directive.ts", + ], + visibility = [ + "//adev/shared-docs/directives:__pkg__", + ], + deps = [ + "//adev/shared-docs/interfaces", + "//packages/core", + "@npm//@angular/cdk", + ], +) diff --git a/adev/shared-docs/directives/search-item/search-item.directive.ts b/adev/shared-docs/directives/search-item/search-item.directive.ts new file mode 100644 index 000000000000..6be2d12228e6 --- /dev/null +++ b/adev/shared-docs/directives/search-item/search-item.directive.ts @@ -0,0 +1,51 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Directive, ElementRef, Input, inject, signal} from '@angular/core'; +import {Highlightable} from '@angular/cdk/a11y'; +import {SearchResult} from '../../interfaces/search-results'; + +@Directive({ + selector: '[docsSearchItem]', + standalone: true, + host: { + '[class.active]': 'isActive', + }, +}) +export class SearchItem implements Highlightable { + @Input() item?: SearchResult; + @Input() disabled = false; + + private readonly elementRef = inject(ElementRef); + + private _isActive = signal(false); + + protected get isActive() { + return this._isActive(); + } + + setActiveStyles(): void { + this._isActive.set(true); + } + + setInactiveStyles(): void { + this._isActive.set(false); + } + + getLabel(): string { + if (!this.item?.hierarchy) { + return ''; + } + const {hierarchy} = this.item; + return `${hierarchy.lvl0}${hierarchy.lvl1}${hierarchy.lvl2}`; + } + + scrollIntoView(): void { + this.elementRef?.nativeElement.scrollIntoView({block: 'nearest'}); + } +} diff --git a/adev/shared-docs/icons/BUILD.bazel b/adev/shared-docs/icons/BUILD.bazel new file mode 100644 index 000000000000..6b848df385f4 --- /dev/null +++ b/adev/shared-docs/icons/BUILD.bazel @@ -0,0 +1,11 @@ +package(default_visibility = ["//visibility:private"]) + +filegroup( + name = "icons", + srcs = glob([ + "*.svg", + ]), + visibility = [ + "//adev/shared-docs:__pkg__", + ], +) diff --git a/adev/shared-docs/icons/chevron.svg b/adev/shared-docs/icons/chevron.svg new file mode 100644 index 000000000000..ef2a71d4b79c --- /dev/null +++ b/adev/shared-docs/icons/chevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/adev/shared-docs/icons/github.svg b/adev/shared-docs/icons/github.svg new file mode 100644 index 000000000000..fe1edac2eaed --- /dev/null +++ b/adev/shared-docs/icons/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/adev/shared-docs/icons/twitter.svg b/adev/shared-docs/icons/twitter.svg new file mode 100644 index 000000000000..0cda36e3da24 --- /dev/null +++ b/adev/shared-docs/icons/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/adev/shared-docs/icons/youtube.svg b/adev/shared-docs/icons/youtube.svg new file mode 100644 index 000000000000..ac27400116cb --- /dev/null +++ b/adev/shared-docs/icons/youtube.svg @@ -0,0 +1,3 @@ + + + diff --git a/adev/shared-docs/index.bzl b/adev/shared-docs/index.bzl new file mode 100644 index 000000000000..d5f9adecde90 --- /dev/null +++ b/adev/shared-docs/index.bzl @@ -0,0 +1,9 @@ +load("//adev/shared-docs/pipeline:_guides.bzl", _generate_guides = "generate_guides") +load("//adev/shared-docs/pipeline:_stackblitz.bzl", _generate_stackblitz = "generate_stackblitz") +load("//adev/shared-docs/pipeline:_playground.bzl", _generate_playground = "generate_playground") +load("//adev/shared-docs/pipeline:_tutorial.bzl", _generate_tutorial = "generate_tutorial") + +generate_guides = _generate_guides +generate_stackblitz = _generate_stackblitz +generate_playground = _generate_playground +generate_tutorial = _generate_tutorial diff --git a/adev/shared-docs/index.ts b/adev/shared-docs/index.ts new file mode 100644 index 000000000000..7d228f59fab2 --- /dev/null +++ b/adev/shared-docs/index.ts @@ -0,0 +1,19 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/* + * Public API Surface of shared + */ + +export * from './directives/index'; +export * from './components/index'; +export * from './constants/index'; +export * from './interfaces/index'; +export * from './providers/index'; +export * from './services/index'; +export * from './utils/index'; diff --git a/adev/shared-docs/interfaces/BUILD.bazel b/adev/shared-docs/interfaces/BUILD.bazel new file mode 100644 index 000000000000..12b42f0a15ad --- /dev/null +++ b/adev/shared-docs/interfaces/BUILD.bazel @@ -0,0 +1,32 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "interfaces", + srcs = [ + "index.ts", + ], + visibility = ["//adev/shared-docs:__subpackages__"], + deps = [ + ":lib", + ], +) + +ts_library( + name = "lib", + srcs = glob( + [ + "**/*.ts", + ], + exclude = [ + "index.ts", + "**/*.spec.ts", + ], + ), + deps = [ + "//packages/core", + "@npm//@types/node", + "@npm//@webcontainer/api", + ], +) diff --git a/adev/shared-docs/interfaces/algolia-config.ts b/adev/shared-docs/interfaces/algolia-config.ts new file mode 100644 index 000000000000..341f2c9263ee --- /dev/null +++ b/adev/shared-docs/interfaces/algolia-config.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export interface AlgoliaConfig { + apiKey: string; + appId: string; + indexName: string; +} diff --git a/adev/shared-docs/interfaces/code-example.ts b/adev/shared-docs/interfaces/code-example.ts new file mode 100644 index 000000000000..5efe4435190b --- /dev/null +++ b/adev/shared-docs/interfaces/code-example.ts @@ -0,0 +1,40 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Type} from '@angular/core'; + +/** + * Map of the examples, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component + */ +export interface CodeExamplesMap { + [id: string]: () => Promise>; +} + +export interface Snippet { + /** Title of the code snippet */ + title?: string; + /** Name of the file. */ + name: string; + /** Content of code snippet */ + content: string; + /** Text in following format `start-end`. Start and end are numbers, based on them provided range of lines will be displayed in collapsed mode */ + visibleLinesRange?: string; +} + +export interface ExampleMetadata { + /** Numeric id of example, used to generate unique link to the example */ + id: number; + /** Title of the example. */ + title?: string; + /** Path to the preview component */ + path?: string; + /** List of files which are part of the example. */ + files: Snippet[]; + /** True when ExampleViewer should have preview */ + preview: boolean; +} diff --git a/adev/shared-docs/interfaces/doc-content.ts b/adev/shared-docs/interfaces/doc-content.ts new file mode 100644 index 000000000000..cb379b5aca02 --- /dev/null +++ b/adev/shared-docs/interfaces/doc-content.ts @@ -0,0 +1,17 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/** Represents a documentation page data. */ +export interface DocContent { + /** The unique identifier for this document. */ + id: string; + /** The HTML to display in the doc viewer. */ + contents: string; + /** The unique title for this document page. */ + title?: string; +} diff --git a/adev/shared-docs/interfaces/docs-content-loader.ts b/adev/shared-docs/interfaces/docs-content-loader.ts new file mode 100644 index 000000000000..6f4ac99ab646 --- /dev/null +++ b/adev/shared-docs/interfaces/docs-content-loader.ts @@ -0,0 +1,14 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {DocContent} from './doc-content'; + +/** The service responsible for fetching static content for docs pages */ +export interface DocsContentLoader { + getContent(path: string): Promise; +} diff --git a/adev/shared-docs/interfaces/environment.ts b/adev/shared-docs/interfaces/environment.ts new file mode 100644 index 000000000000..969078e33010 --- /dev/null +++ b/adev/shared-docs/interfaces/environment.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {AlgoliaConfig} from './algolia-config'; + +export interface Environment { + production: boolean; + algolia: AlgoliaConfig; + googleAnalyticsId: string; +} diff --git a/adev/shared-docs/interfaces/example-viewer-content-loader.ts b/adev/shared-docs/interfaces/example-viewer-content-loader.ts new file mode 100644 index 000000000000..393274647c94 --- /dev/null +++ b/adev/shared-docs/interfaces/example-viewer-content-loader.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Type} from '@angular/core'; + +/** The service responsible for fetching the type of Component to display in the preview */ +export interface ExampleViewerContentLoader { + /** Returns type of Component to display in the preview */ + loadPreview(id: string): Promise>; +} diff --git a/adev/shared-docs/interfaces/index.ts b/adev/shared-docs/interfaces/index.ts new file mode 100644 index 000000000000..a11862c30e05 --- /dev/null +++ b/adev/shared-docs/interfaces/index.ts @@ -0,0 +1,17 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './code-example'; +export * from './doc-content'; +export * from './docs-content-loader'; +export * from './example-viewer-content-loader'; +export * from './environment'; +export * from './navigation-item'; +export * from './table-of-contents-item'; +export * from './search-results'; +export * from './tutorial'; diff --git a/adev/shared-docs/interfaces/navigation-item.ts b/adev/shared-docs/interfaces/navigation-item.ts new file mode 100644 index 000000000000..0dfdd177e61c --- /dev/null +++ b/adev/shared-docs/interfaces/navigation-item.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export interface NavigationItem { + label?: string; + path?: string; + children?: NavigationItem[]; + isExternal?: boolean; + isExpanded?: boolean; + level?: number; + parent?: NavigationItem; + contentPath?: string; +} diff --git a/adev/shared-docs/interfaces/search-results.ts b/adev/shared-docs/interfaces/search-results.ts new file mode 100644 index 000000000000..a5beb9209196 --- /dev/null +++ b/adev/shared-docs/interfaces/search-results.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/* The interface represents Algolia search result item. */ +export interface SearchResult { + /* The url link to the search result page */ + url?: string; + /* The hierarchy of the item */ + hierarchy?: Hierarchy; + /* The unique id of the search result item */ + objectID: string; +} + +/* The hierarchy of the item */ +export interface Hierarchy { + /* It's kind of the page i.e `Docs`, `Tutorials`, `Reference` etc. */ + lvl0: string | null; + /* Typicaly it's the content of H1 of the page */ + lvl1: string | null; + /* Typicaly it's the content of H2 of the page */ + lvl2: string | null; + lvl3: string | null; + lvl4: string | null; + lvl5: string | null; + lvl6: string | null; +} diff --git a/adev/shared-docs/interfaces/table-of-contents-item.ts b/adev/shared-docs/interfaces/table-of-contents-item.ts new file mode 100644 index 000000000000..11bf1f256dcc --- /dev/null +++ b/adev/shared-docs/interfaces/table-of-contents-item.ts @@ -0,0 +1,24 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export enum TableOfContentsLevel { + H2 = 'h2', + H3 = 'h3', +} + +/** Represents a table of content item. */ +export interface TableOfContentsItem { + /** The url fragment of specific section */ + id: string; + /** The level of the item. */ + level: TableOfContentsLevel; + /** The unique title for this document page. */ + title: string; + /** The top offset px of the heading */ + top: number; +} diff --git a/adev/shared-docs/interfaces/tutorial.ts b/adev/shared-docs/interfaces/tutorial.ts new file mode 100644 index 000000000000..69f9f30d11f0 --- /dev/null +++ b/adev/shared-docs/interfaces/tutorial.ts @@ -0,0 +1,164 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import type {FileSystemTree} from '@webcontainer/api'; +import {NavigationItem} from './navigation-item'; + +/** the step is used only in this function to sort the nav items */ +export type TutorialNavigationItemWithStep = TutorialNavigationItem & { + tutorialData: TutorialNavigationData & { + step: TutorialStep['step']; + }; +}; + +/** + * Represents the contents of the tutorial files to be generated by the build script + */ +export type TutorialFiles = { + sourceCode?: FileSystemTree; + metadata?: TutorialMetadata; + sourceCodeZip?: Uint8Array; + route?: Omit; +}; + +export type PlaygroundFiles = { + sourceCode?: FileSystemTree; + metadata?: TutorialMetadata; + sourceCodeZip?: undefined; + route?: PlaygroundRouteData; +}; + +/** Represents the contents of the tutorial config file */ +export type TutorialMetadata = { + type: TutorialConfig['type']; + + /** a record of all tutorials filenames and its contents */ + tutorialFiles: FileAndContentRecord; + + /** a record of filenames and contents for the tutorial answer */ + answerFiles?: FileAndContentRecord; + + /** files that are part of the project but are not visible in the code editor */ + hiddenFiles: string[]; + + /** + * All files in the project, used to find the difference between new and old projects + * when changing projects + */ + allFiles: string[]; + + openFiles: NonNullable; + + /** whether a package.json exists */ + dependencies?: Record; +}; + +export type TutorialStep = { + step: number; + name: string; + path: string; + url: string; + nextStep?: TutorialStep['url']; + previousStep?: TutorialStep['url']; + nextTutorial?: string; +}; + +export type TutorialConfig = + | EditorTutorialConfig + | LocalTutorialConfig + | CliTutorialConfig + | EditorOnlyTutorialConfig; + +export interface TutorialConfigBase { + type: TutorialType; + + /** The tutorial title */ + title: string; + + /** The name of the tutorial folder that will be started after the current one ends. */ + nextTutorial?: string; + + /** The path to the tutorial src folder when it's external to the tutorial */ + src?: string; + + /** The path to the tutorial answer folder when it's external to the tutorial */ + answerSrc?: string; + + /** An array of files to be open in the editor */ + openFiles?: string[]; +} + +/** Represents a tutorial config with all the embedded editor components enabled */ +export interface EditorTutorialConfig extends TutorialConfigBase { + type: TutorialType.EDITOR; +} + +/** Represents a tutorial config that won't use the embedded editor */ +export interface LocalTutorialConfig extends TutorialConfigBase { + type: TutorialType.LOCAL; + + // fields that must be undefined for local app tutorials + openFiles?: undefined; + src?: undefined; + answerSrc?: undefined; +} + +/** Represents a tutorial config that supports only the interactive terminal for the Angular CLI */ +export type CliTutorialConfig = Omit & { + type: TutorialType.CLI; +}; + +export type EditorOnlyTutorialConfig = Omit & { + type: TutorialType.EDITOR_ONLY; +}; + +export type FileAndContent = { + path: string; + content: string | Uint8Array; +}; + +export type FileAndContentRecord = Record; + +export type TutorialNavigationItem = { + path: NonNullable; + label: NonNullable; + children?: TutorialNavigationItem[]; + parent?: TutorialNavigationItem; + contentPath?: string; + tutorialData: TutorialNavigationData; +}; + +export type TutorialNavigationData = { + type: TutorialConfig['type']; + title: TutorialConfig['title']; + nextStep?: string; + previousStep?: string; + nextTutorial?: string; + sourceCodeZipPath?: string; +}; + +export type PlaygroundRouteData = { + templates: PlaygroundTemplate[]; + defaultTemplate?: PlaygroundTemplate; + starterTemplate?: PlaygroundTemplate; +}; + +export type PlaygroundTemplate = Required>; + +// Note: only the fields being used are defined in this type +export interface PackageJson { + dependencies: Record; + devDependencies: Record; +} + +export const enum TutorialType { + CLI = 'cli', + LOCAL = 'local', + EDITOR = 'editor', + EDITOR_ONLY = 'editor-only', +} diff --git a/adev/shared-docs/package.json b/adev/shared-docs/package.json new file mode 100644 index 000000000000..f7ccb18e5333 --- /dev/null +++ b/adev/shared-docs/package.json @@ -0,0 +1,32 @@ +{ + "name": "@angular/docs", + "version": "0.0.0-{SCM_HEAD_SHA}", + "peerDependencies": { + "@angular/cdk": "^18.0.0-next.6 || ^18.1.0-next || ^18.2.0-next", + "@angular/common": "^18.0.0-next.6 || ^18.1.0-next || ^18.2.0-next", + "@angular/core": "^18.0.0-next.6 || ^18.1.0-next || ^18.2.0-next", + "@angular/forms": "^18.0.0-next.6 || ^18.1.0-next || ^18.2.0-next", + "@angular/material": "^18.0.0-next.6 || ^18.1.0-next || ^18.2.0-next", + "@angular/platform-browser": "^18.0.0-next.6 || ^18.1.0-next || ^18.2.0-next", + "@angular/router": "^18.0.0-next.6 || ^18.1.0-next || ^18.2.0-next", + "algoliasearch": "^4.20.0", + "rxjs": "^7.8.1" + }, + "dependencies": { + "@webcontainer/api": "^1.1.8", + "diff": "~5.2.0", + "emoji-regex": "~10.3.0", + "fast-glob": "~3.3.2", + "fflate": "^0.8.2", + "html-entities": "~2.5.2", + "jsdom": "~24.1.0", + "marked": "~12.0.2", + "mermaid": "^10.8.0", + "shiki": "^1.10.3" + }, + "exports": { + "./styles/*": { + "sass": "./styles/*" + } + } +} diff --git a/adev/shared-docs/pipeline/BUILD.bazel b/adev/shared-docs/pipeline/BUILD.bazel new file mode 100644 index 000000000000..92accc4468f6 --- /dev/null +++ b/adev/shared-docs/pipeline/BUILD.bazel @@ -0,0 +1,162 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") + +# BEGIN-DEV-ONLY +# This section is only used here in the dev-infra repo as the resulting bundle is published in +# the package. +load("//tools:defaults.bzl", "esbuild_esm_bundle") + +package(default_visibility = ["//adev/shared-docs/pipeline:__subpackages__"]) + +esbuild_esm_bundle( + name = "guides", + define = { + "HANDLE_MERMAID": "true", + }, + entry_point = "//adev/shared-docs/pipeline/guides:index.ts", + # JSDOM should not be bundled because it has workers and dynamic imports. + # playwright-core cannot be bundled because of its cjs usage. + external = [ + "jsdom", + "playwright-core", + ], + output = "guides.mjs", + platform = "node", + target = "es2022", + visibility = ["//visibility:public"], + deps = [ + "//adev/shared-docs/pipeline/guides:index", + ], +) + +esbuild_esm_bundle( + name = "guides_no_mermaid", + define = { + "HANDLE_MERMAID": "false", + }, + entry_point = "//adev/shared-docs/pipeline/guides:index.ts", + # JSDOM should not be bundled because it has workers and dynamic imports. + external = [ + "jsdom", + ], + output = "guides-no-mermaid.mjs", + platform = "node", + target = "es2022", + visibility = ["//visibility:public"], + deps = [ + "//adev/shared-docs/pipeline/guides:index", + ], +) + +esbuild_esm_bundle( + name = "stackblitz-bundle", + entry_point = "//adev/shared-docs/pipeline/examples/stackblitz:index.ts", + # JSDOM should not be bundled because it has workers and dynamic imports. + external = ["jsdom"], + output = "stackblitz.mjs", + platform = "node", + target = "es2022", + visibility = ["//visibility:public"], + deps = [ + "//adev/shared-docs/pipeline/examples/stackblitz:index", + ], +) + +esbuild_esm_bundle( + name = "zip-bundle", + entry_point = "//adev/shared-docs/pipeline/examples/zip:index.ts", + output = "zip.mjs", + platform = "node", + target = "es2022", + visibility = ["//visibility:public"], + deps = [ + "//adev/shared-docs/pipeline/examples/zip:index", + ], +) + +esbuild_esm_bundle( + name = "playground-bundle", + entry_point = "//adev/shared-docs/pipeline/tutorials:playground.ts", + output = "playground.mjs", + platform = "node", + target = "es2022", + visibility = ["//visibility:public"], + deps = [ + "//adev/shared-docs/pipeline/tutorials:playground", + ], +) + +esbuild_esm_bundle( + name = "tutorial-bundle", + entry_point = "//adev/shared-docs/pipeline/tutorials:tutorial.ts", + output = "tutorial.mjs", + platform = "node", + target = "es2022", + visibility = ["//visibility:public"], + deps = [ + "//adev/shared-docs/pipeline/tutorials", + ], +) + +exports_files([ + "_guides.bzl", + "_stackblitz.bzl", + "_playground.bzl", + "_tutorial.bzl", + "BUILD.bazel", +]) + +# END-DEV-ONLY + +nodejs_binary( + name = "stackblitz", + data = [ + "@npm//jsdom", + ], + entry_point = "//adev/shared-docs/pipeline:stackblitz.mjs", + visibility = ["//visibility:public"], +) + +nodejs_binary( + name = "zip", + entry_point = "//adev/shared-docs/pipeline:zip.mjs", + visibility = ["//visibility:public"], +) + +nodejs_binary( + name = "markdown", + data = [ + "@npm//@angular/build-tooling/bazel/browsers/chromium:chromium-for-generation", + "@npm//jsdom", + "@npm//mermaid", + "@npm//playwright-core", + ], + entry_point = "//adev/shared-docs/pipeline:guides.mjs", + env = { + "CHROME_BIN": "$(CHROMIUM)", + }, + toolchains = [ + "@npm//@angular/build-tooling/bazel/browsers/chromium:toolchain_alias", + ], + visibility = ["//visibility:public"], +) + +nodejs_binary( + name = "markdown_no_mermaid", + data = [ + "@npm//jsdom", + ], + entry_point = "//adev/shared-docs/pipeline:guides-no-mermaid.mjs", + visibility = ["//visibility:public"], +) + +nodejs_binary( + name = "playground", + entry_point = "//adev/shared-docs/pipeline:playground.mjs", + visibility = ["//visibility:public"], +) + +nodejs_binary( + name = "tutorial", + entry_point = "//adev/shared-docs/pipeline:tutorial.mjs", + visibility = ["//visibility:public"], +) diff --git a/adev/shared-docs/pipeline/_guides.bzl b/adev/shared-docs/pipeline/_guides.bzl new file mode 100644 index 000000000000..f6b0628d09c3 --- /dev/null +++ b/adev/shared-docs/pipeline/_guides.bzl @@ -0,0 +1,90 @@ +load("@build_bazel_rules_nodejs//:providers.bzl", "run_node") +load("@npm//@angular/build-tooling/bazel/private:path_relative_to_label.bzl", "path_relative_to_label") + +def _generate_guides(ctx): + """Implementation of the markdown rule""" + + # Define arguments that will be passed to the underlying nodejs program. + args = ctx.actions.args() + + # Use a param file because we may have a large number of inputs. + args.set_param_file_format("multiline") + args.use_param_file("%s", use_always = True) + + # Pass the set of source files. + args.add_joined(ctx.files.srcs, join_with = ",") + + # Pass the the output directory path (which is the bazel-bin directory). + args.add(ctx.bin_dir.path) + + # Determine the set of html output files. For each input markdown file, produce an html + # file with the same name (replacing the markdown extension with `.html`). + html_outputs = [] + for input_file in ctx.files.srcs: + # Determine the input file path relatively to the current package path. We do this + # because we want to preserve directories for the input files and `declare_file` expects a + # path that is relative to the current package. We don't use `.replace` + # here because the extension can be also in upper case. + relative_basepath = path_relative_to_label(ctx.label, input_file.short_path) + + # For each input file "xxx.md", we want to write an output file "xxx.html" + html_outputs += [ctx.actions.declare_file("%s.html" % relative_basepath)] + + # Define an action that runs the nodejs_binary executable. This is the main thing that this + # rule does. If mermaid blocks are enabled then a different executable is used. + if (ctx.attr.mermaid_blocks): + run_node( + ctx = ctx, + inputs = depset(ctx.files.srcs + ctx.files.data), + executable = "_generate_guides", + outputs = html_outputs, + arguments = [args], + ) + else: + run_node( + ctx = ctx, + inputs = depset(ctx.files.srcs + ctx.files.data), + executable = "_generate_guides_no_mermaid", + outputs = html_outputs, + arguments = [args], + ) + + # The return value describes what the rule is producing. In this case we need to specify + # the "DefaultInfo" with the output html files. + return [DefaultInfo(files = depset(html_outputs))] + +generate_guides = rule( + # Point to the starlark function that will execute for this rule. + implementation = _generate_guides, + doc = """Rule that renders markdown sources to html""", + + # The attributes that can be set to this rule. + attrs = { + "srcs": attr.label_list( + doc = """Markdown sources to render to html.""", + allow_empty = False, + allow_files = True, + ), + "data": attr.label_list( + doc = """Source referenced from within the markdown.""", + allow_files = True, + ), + "mermaid_blocks": attr.bool( + doc = """Whether to transform mermaid blocks.""", + default = False, + ), + + # The executable for this rule (private). + "_generate_guides": attr.label( + default = Label("//adev/shared-docs/pipeline:markdown"), + executable = True, + cfg = "exec", + ), + # The executable for this rule when no mermaid process is required (private). + "_generate_guides_no_mermaid": attr.label( + default = Label("//adev/shared-docs/pipeline:markdown_no_mermaid"), + executable = True, + cfg = "exec", + ), + }, +) diff --git a/adev/shared-docs/pipeline/_playground.bzl b/adev/shared-docs/pipeline/_playground.bzl new file mode 100644 index 000000000000..a0b866b58baa --- /dev/null +++ b/adev/shared-docs/pipeline/_playground.bzl @@ -0,0 +1,64 @@ +load("@build_bazel_rules_nodejs//:providers.bzl", "run_node") + +def _generate_playground(ctx): + """Implementation of the playground generator rule""" + + # Determine the common directory base + # Create an initial string of length greater than the length the common directory path will + # be. This is used so that the first file path we encounter is used as the initial value. + common_srcs = " " * 10000 + for file in ctx.files.common_srcs: + file_path = file.dirname + if (len(file_path) < len(common_srcs)): + common_srcs = file_path + + # The directory being generated into + playground_directory = ctx.actions.declare_directory(ctx.label.name) + + # Set the arguments for the actions inputs and output location. + args = ctx.actions.args() + + # Path to the playground being generated. + args.add(ctx.attr.playground_srcs.label.package) + + # Path to the common directory + args.add(common_srcs) + + # Path to the html output file to write to. + args.add(playground_directory.path) + + ctx.runfiles(files = ctx.files.common_srcs) + + run_node( + ctx = ctx, + inputs = depset(ctx.files.playground_srcs + ctx.files.common_srcs), + executable = "_generate_playground", + outputs = [playground_directory], + arguments = [args], + ) + + # The return value describes what the rule is producing. In this case we need to specify + # the "DefaultInfo" with the output html files. + return [DefaultInfo(files = depset([playground_directory]))] + +generate_playground = rule( + # Point to the starlark function that will execute for this rule. + implementation = _generate_playground, + doc = """Rule that generates playground source and configuration files.""", + + # The attributes that can be set to this rule. + attrs = { + "playground_srcs": attr.label( + doc = """Files used for the playground generation.""", + ), + "common_srcs": attr.label( + doc = """The directory containing the base files to expand upon.""", + default = Label("//adev/shared-docs/pipeline/tutorials/common:files"), + ), + "_generate_playground": attr.label( + default = Label("//adev/shared-docs/pipeline:playground"), + executable = True, + cfg = "exec", + ), + }, +) diff --git a/adev/shared-docs/pipeline/_stackblitz.bzl b/adev/shared-docs/pipeline/_stackblitz.bzl new file mode 100644 index 000000000000..19df35c972fd --- /dev/null +++ b/adev/shared-docs/pipeline/_stackblitz.bzl @@ -0,0 +1,70 @@ +load("@build_bazel_rules_nodejs//:providers.bzl", "run_node") + +def _generate_stackblitz(ctx): + """Implementation of the stackblitz generator rule""" + + # Determine the stackblitz template base directory + # Create an initial string of length greater than the length the stackblitz template path will + # be. This is used so that the first file path we encounter is used as the initial value. + template_srcs = " " * 10000 + for file in ctx.files.template_srcs: + file_path = file.dirname + if (len(file_path) < len(template_srcs)): + template_srcs = file_path + + # File declaration of the generated html file + html_output = ctx.actions.declare_file("%s.html" % ctx.attr.name) + + # Temporary directory for the generation to utilize + tmp_directory = ctx.actions.declare_directory("TMP_" + ctx.label.name) + + # Set the arguments for the actions inputs and output location. + args = ctx.actions.args() + + # Path to the example being generated. + args.add(ctx.attr.example_srcs.label.package) + + # Path to the actions temporary directory. + args.add(tmp_directory.short_path) + + # Path to the stackblitz template + args.add(template_srcs) + + # Path to the html output file to write to. + args.add(html_output.path) + + ctx.runfiles(files = ctx.files.template_srcs) + + run_node( + ctx = ctx, + inputs = depset(ctx.files.example_srcs + ctx.files.template_srcs), + executable = "_generate_stackblitz", + outputs = [html_output, tmp_directory], + arguments = [args], + ) + + # The return value describes what the rule is producing. In this case we need to specify + # the "DefaultInfo" with the output html files. + return [DefaultInfo(files = depset([html_output]))] + +generate_stackblitz = rule( + # Point to the starlark function that will execute for this rule. + implementation = _generate_stackblitz, + doc = """Rule that generates stackblitz post API html opener""", + + # The attributes that can be set to this rule. + attrs = { + "example_srcs": attr.label( + doc = """Files used for the stackblitz generation.""", + ), + "template_srcs": attr.label( + doc = """The stackblitz template directory to base generated stackblitz on.""", + default = Label("//adev/shared-docs/pipeline/examples/template:files"), + ), + "_generate_stackblitz": attr.label( + default = Label("//adev/shared-docs/pipeline:stackblitz"), + executable = True, + cfg = "exec", + ), + }, +) diff --git a/adev/shared-docs/pipeline/_tutorial.bzl b/adev/shared-docs/pipeline/_tutorial.bzl new file mode 100644 index 000000000000..add742659697 --- /dev/null +++ b/adev/shared-docs/pipeline/_tutorial.bzl @@ -0,0 +1,64 @@ +load("@build_bazel_rules_nodejs//:providers.bzl", "run_node") + +def _generate_tutorial(ctx): + """Implementation of the tutorial generator rule""" + + # Determine the common directory base + # Create an initial string of length greater than the length the common directory path will + # be. This is used so that the first file path we encounter is used as the initial value. + common_srcs = " " * 10000 + for file in ctx.files.common_srcs: + file_path = file.dirname + if (len(file_path) < len(common_srcs)): + common_srcs = file_path + + # The directory being generated into + tutorial_directory = ctx.actions.declare_directory(ctx.label.name) + + # Set the arguments for the actions inputs and output location. + args = ctx.actions.args() + + # Path to the tutorial being generated. + args.add(ctx.attr.tutorial_srcs.label.package) + + # Path to the common directory + args.add(common_srcs) + + # Path to the html output file to write to. + args.add(tutorial_directory.path) + + ctx.runfiles(files = ctx.files.common_srcs) + + run_node( + ctx = ctx, + inputs = depset(ctx.files.tutorial_srcs + ctx.files.common_srcs), + executable = "_generate_tutorial", + outputs = [tutorial_directory], + arguments = [args], + ) + + # The return value describes what the rule is producing. In this case we need to specify + # the "DefaultInfo" with the output html files. + return [DefaultInfo(files = depset([tutorial_directory]))] + +generate_tutorial = rule( + # Point to the starlark function that will execute for this rule. + implementation = _generate_tutorial, + doc = """Rule that generates tutorial source and configuration files.""", + + # The attributes that can be set to this rule. + attrs = { + "tutorial_srcs": attr.label( + doc = """Files used for the tutorial generation.""", + ), + "common_srcs": attr.label( + doc = """The directory containing the base files to expand upon.""", + default = Label("//adev/shared-docs/pipeline/tutorials/common:files"), + ), + "_generate_tutorial": attr.label( + default = Label("//adev/shared-docs/pipeline:tutorial"), + executable = True, + cfg = "exec", + ), + }, +) diff --git a/adev/shared-docs/pipeline/_zip.bzl b/adev/shared-docs/pipeline/_zip.bzl new file mode 100644 index 000000000000..f965b9f4c60f --- /dev/null +++ b/adev/shared-docs/pipeline/_zip.bzl @@ -0,0 +1,70 @@ +load("@build_bazel_rules_nodejs//:providers.bzl", "run_node") + +def _generate_zip(ctx): + """Implementation of the zip generator rule""" + + # Determine the template base directory + # Create an initial string of length greater than the length the template path will + # be. This is used so that the first file path we encounter is used as the initial value. + template_srcs = " " * 10000 + for file in ctx.files.template_srcs: + file_path = file.dirname + if (len(file_path) < len(template_srcs)): + template_srcs = file_path + + # File declaration of the generated zip file + zip_output = ctx.actions.declare_file("%s.zip" % ctx.attr.name) + + # Temporary directory for the generation to utilize + tmp_directory = ctx.actions.declare_directory("TMP_" + ctx.label.name) + + # Set the arguments for the actions inputs and output location. + args = ctx.actions.args() + + # Path to the example being generated. + args.add(ctx.attr.example_srcs.label.package) + + # Path to the actions temporary directory. + args.add(tmp_directory.short_path) + + # Path to the stackblitz template + args.add(template_srcs) + + # Path to the html output file to write to. + args.add(zip_output.path) + + ctx.runfiles(files = ctx.files.template_srcs) + + run_node( + ctx = ctx, + inputs = depset(ctx.files.example_srcs + ctx.files.template_srcs), + executable = "_generate_zip", + outputs = [zip_output, tmp_directory], + arguments = [args], + ) + + # The return value describes what the rule is producing. In this case we need to specify + # the "DefaultInfo" with the output html files. + return [DefaultInfo(files = depset([zip_output]))] + +generate_zip = rule( + # Point to the starlark function that will execute for this rule. + implementation = _generate_zip, + doc = """Rule that generates zip of example directory""", + + # The attributes that can be set to this rule. + attrs = { + "example_srcs": attr.label( + doc = """Files used for the zip generation.""", + ), + "template_srcs": attr.label( + doc = """The template directory to base zip on.""", + default = Label("//adev/shared-docs/pipeline/examples/template:files"), + ), + "_generate_zip": attr.label( + default = Label("//adev/shared-docs/pipeline:zip"), + executable = True, + cfg = "exec", + ), + }, +) diff --git a/adev/shared-docs/pipeline/examples/shared/BUILD.bazel b/adev/shared-docs/pipeline/examples/shared/BUILD.bazel new file mode 100644 index 000000000000..ca45f14fe463 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/shared/BUILD.bazel @@ -0,0 +1,15 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "shared", + srcs = glob( + [ + "*.ts", + ], + ), + deps = [ + "@npm//@types/node", + ], +) diff --git a/adev/shared-docs/pipeline/examples/shared/copyright.ts b/adev/shared-docs/pipeline/examples/shared/copyright.ts new file mode 100644 index 000000000000..8b497fd5a16c --- /dev/null +++ b/adev/shared-docs/pipeline/examples/shared/copyright.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/** The pad used around the copyright blocks. */ +const PAD = '\n\n'; +/** The standard copyright block used throughout Angular. */ +const COPYRIGHT = ` +@license +Copyright Google LLC All Rights Reserved. + +Use of this source code is governed by an MIT-style license that can be +found in the LICENSE file at https://angular.dev/license +`; +/** Copyright comment for CSS and Javascript/Typescript files. */ +const CSS_TS_COPYRIGHT = `/*${COPYRIGHT}*/${PAD}`; +/** Copyright comment for HTML files. */ +const HTML_COPYRIGHT = `${PAD}`; + +/** + * Append the copyright using the appropriate commenting structure based on the file extension. + * + * No copyright is appended if the type is unrecognized. + */ +export function appendCopyrightToFile(filename: string, content: string): string { + const extension = filename.split('.').pop(); + switch (extension) { + case 'html': + case 'ng': + return `${HTML_COPYRIGHT}${content}`; + case 'js': + case 'ts': + case 'css': + case 'scss': + return `${CSS_TS_COPYRIGHT}${content}`; + default: + return content; + } +} diff --git a/adev/shared-docs/pipeline/examples/shared/file-system.ts b/adev/shared-docs/pipeline/examples/shared/file-system.ts new file mode 100644 index 000000000000..9aba9c049e1e --- /dev/null +++ b/adev/shared-docs/pipeline/examples/shared/file-system.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {existsSync} from 'fs'; +import {copyFile, mkdir, readdir, rm, stat} from 'fs/promises'; +import {join} from 'path'; + +// TODO(josephperrott): Determine if we can use the fs default version of copying directories. +// TODO(josephperrott): Find a way to not require blank renaming of certain files during copying. + +/** Files which must be renamed to remove the .template suffix. */ +const knownTemplateFilesForRename = [ + // package.json.template must be used as ng_package does not allow floating package.json + // files within the npm package contents. + 'package.json.template', +]; + +/** + * Recursively copy folder and contents to the destigation, creating the destination folder + * if necessary. + **/ +export async function copyFolder(source: string, destination: string) { + if (!existsSync(destination)) { + await mkdir(destination, {recursive: true}); + } + + const files = await readdir(source); + + for (const file of files) { + // If the file/dirname starts with `TMP_` we ignore it as we use `TMP_` to start the name of + // our temp directory. Since our temp directory is a subdirectory of the provided example, + // we would end up copying recursively forever. + if (file.startsWith('TMP_')) { + continue; + } + + const sourcePath = join(source, file); + let destPath = join(destination, file); + + // Rename the destination file path if the file needs to be renamed. + if (knownTemplateFilesForRename.includes(file)) { + destPath = join(destination, file.replace(/.template$/, '')); + } + + const stats = await stat(sourcePath); + const isDirectory = await stats.isDirectory(); + + if (isDirectory) { + await copyFolder(sourcePath, destPath); + } else { + await copyFile(sourcePath, destPath); + } + } +} + +/** Create folder at the provided path if it does not already exist. */ +export async function createFolder(path: string): Promise { + if (!existsSync(path)) { + await mkdir(path, {recursive: true}); + } +} + +/** Remove folder at the provided path if it exists. */ +export async function removeFolder(path: string): Promise { + if (existsSync(path)) { + await rm(path, {recursive: true}); + } +} diff --git a/adev/shared-docs/pipeline/examples/stackblitz/BUILD.bazel b/adev/shared-docs/pipeline/examples/stackblitz/BUILD.bazel new file mode 100644 index 000000000000..93907a5bf947 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/stackblitz/BUILD.bazel @@ -0,0 +1,41 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "stackblitz", + srcs = glob( + [ + "*.ts", + ], + exclude = [ + "index.ts", + ], + ), + deps = [ + "//adev/shared-docs/pipeline/examples/shared", + "//adev/shared-docs/pipeline/guides", + "@npm//@types/jsdom", + "@npm//@types/node", + "@npm//fast-glob", + "@npm//jsdom", + ], +) + +ts_library( + name = "index", + srcs = [ + "index.ts", + ], + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + ":stackblitz", + "@npm//@types/node", + ], +) + +exports_files([ + "index.ts", +]) diff --git a/adev/shared-docs/pipeline/examples/stackblitz/builder.ts b/adev/shared-docs/pipeline/examples/stackblitz/builder.ts new file mode 100644 index 000000000000..d6616e4db8a3 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/stackblitz/builder.ts @@ -0,0 +1,176 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {join} from 'path'; +import {readFile} from 'fs/promises'; +import {copyFolder, createFolder, removeFolder} from '../shared/file-system'; +import jsdom from 'jsdom'; +import {glob} from 'fast-glob'; +import {regionParser} from '../../guides/extensions/docs-code/regions/region-parser'; +import {appendCopyrightToFile} from '../shared/copyright'; +import {FileType} from '../../guides/extensions/docs-code/sanitizers/eslint'; +import {EXCLUDE_FILES_FOR_STACKBLITZ, STACKBLITZ_CONFIG_FILENAME} from './defaults'; + +interface StackblitzConfig { + title: string; + ignore: string[]; + file: string; + tags: string[]; + description: string; +} + +export async function generateStackblitzExample( + exampleDir: string, + temporaryExampleDir: string, + stackblitzTemplateDir: string, +) { + const config = await readFile(join(exampleDir, STACKBLITZ_CONFIG_FILENAME), 'utf-8'); + const stackblitzConfig: StackblitzConfig = JSON.parse(config) as StackblitzConfig; + + await createFolder(temporaryExampleDir); + + // Copy template files to TEMP folder + await copyFolder(stackblitzTemplateDir, temporaryExampleDir); + + // Copy example files to TEMP folder + await copyFolder(exampleDir, temporaryExampleDir); + const result = await generateStackblitzHtml(temporaryExampleDir, stackblitzConfig); + await removeFolder(temporaryExampleDir); + return result; +} + +async function generateStackblitzHtml( + temporaryExampleDir: string, + stackBlitzConfig: StackblitzConfig, +): Promise { + const defaultIncludes = [ + '**/*.ts', + '**/*.js', + '**/*.css', + '**/*.html', + '**/*.md', + '**/*.json', + '**/*.svg', + ]; + const exampleFilePaths = await glob(defaultIncludes, { + cwd: temporaryExampleDir, + onlyFiles: true, + dot: true, + ignore: stackBlitzConfig.ignore, + }); + + const postData = await createPostData(temporaryExampleDir, stackBlitzConfig, exampleFilePaths); + const primaryFile = getPrimaryFile(stackBlitzConfig.file, exampleFilePaths); + return createStackblitzHtml(postData, primaryFile); +} + +function getPrimaryFile(primaryFilePath: string, exampleFilePaths: string[]): string { + if (primaryFilePath) { + if (!exampleFilePaths.some((filePath) => filePath === primaryFilePath)) { + throw new Error(`The specified primary file (${primaryFilePath}) does not exist!`); + } + return primaryFilePath; + } else { + const defaultPrimaryFilePaths = [ + 'src/app/app.component.html', + 'src/app/app.component.ts', + 'src/app/main.ts', + ]; + const primaryFile = defaultPrimaryFilePaths.find((path) => + exampleFilePaths.some((filePath) => filePath === path), + ); + + if (!primaryFile) { + throw new Error( + `None of the default primary files (${defaultPrimaryFilePaths.join(', ')}) exists.`, + ); + } + + return primaryFile; + } +} + +async function createPostData( + exampleDir: string, + config: StackblitzConfig, + exampleFilePaths: string[], +): Promise> { + const postData: Record = {}; + + for (const filePath of exampleFilePaths) { + if (EXCLUDE_FILES_FOR_STACKBLITZ.some((excludedFile) => filePath.endsWith(excludedFile))) { + continue; + } + + let content = await readFile(join(exampleDir, filePath), 'utf-8'); + content = appendCopyrightToFile(filePath, content); + content = extractRegions(filePath, content); + + postData[`project[files][${filePath}]`] = content; + } + + const tags = ['angular', 'example', ...(config.tags || [])]; + tags.forEach((tag, index) => (postData[`project[tags][${index}]`] = tag)); + + postData['project[description]'] = `Angular Example - ${config.description}`; + postData['project[template]'] = 'node'; + postData['project[title]'] = config.title ?? 'Angular Example'; + + return postData; +} + +function createStackblitzHtml(postData: Record, primaryFile: string): string { + const baseHtml = createBaseStackblitzHtml(primaryFile); + const doc = new jsdom.JSDOM(baseHtml).window.document; + const form = doc.querySelector('form'); + + for (const [key, value] of Object.entries(postData)) { + const element = htmlToElement(doc, ``); + if (element && form) { + element.setAttribute('value', value as string); + form.appendChild(element); + } + } + + return doc.documentElement.outerHTML; +} + +function createBaseStackblitzHtml(primaryFile: string) { + const action = `https://stackblitz.com/run?file=${primaryFile}`; + + return ` + +
+ + + `.trim(); +} + +function htmlToElement(document: Document, html: string) { + const div = document.createElement('div'); + div.innerHTML = html; + return div.firstElementChild; +} + +function extractRegions(path: string, contents: string): string { + const fileType: FileType | undefined = path?.split('.').pop() as FileType; + const regionParserResult = regionParser(contents, fileType); + return regionParserResult.contents; +} diff --git a/adev/shared-docs/pipeline/examples/stackblitz/defaults.ts b/adev/shared-docs/pipeline/examples/stackblitz/defaults.ts new file mode 100644 index 000000000000..5a634fba9faa --- /dev/null +++ b/adev/shared-docs/pipeline/examples/stackblitz/defaults.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export const TEST_FILES_EXTENSION_SUFFIX = '.spec.ts'; +export const TEST_FILES_E2E_EXTENSION_SUFFIX = '.e2e-spec.ts'; +export const BUILD_BAZEL_FILENAME = 'BUILD.bazel'; +export const EXAMPLE_CONFIG_FILENAME = 'example-config.json'; +export const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json'; + +/** Default file paths to be excluded from stackblitz examples. */ +export const EXCLUDE_FILES_FOR_STACKBLITZ = [ + STACKBLITZ_CONFIG_FILENAME, + BUILD_BAZEL_FILENAME, + EXAMPLE_CONFIG_FILENAME, + TEST_FILES_EXTENSION_SUFFIX, + TEST_FILES_E2E_EXTENSION_SUFFIX, +]; diff --git a/adev/shared-docs/pipeline/examples/stackblitz/index.ts b/adev/shared-docs/pipeline/examples/stackblitz/index.ts new file mode 100644 index 000000000000..856c52b4b2a1 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/stackblitz/index.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {writeFileSync} from 'fs'; +import {generateStackblitzExample} from './builder'; + +const [exampleDir, tmpDir, templateDir, outputFilePath] = process.argv.slice(2); +const htmlOutputContent = await generateStackblitzExample(exampleDir, tmpDir, templateDir); + +writeFileSync(outputFilePath, htmlOutputContent, {encoding: 'utf8'}); diff --git a/adev/shared-docs/pipeline/examples/template/.stackblitzrc b/adev/shared-docs/pipeline/examples/template/.stackblitzrc new file mode 100644 index 000000000000..9ff7fea85342 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/.stackblitzrc @@ -0,0 +1,4 @@ +{ + "installDependencies": false, + "startCommand": "npm install --legacy-peer-deps && npm start" +} diff --git a/adev/shared-docs/pipeline/examples/template/BUILD.bazel b/adev/shared-docs/pipeline/examples/template/BUILD.bazel new file mode 100644 index 000000000000..706e35dbb1c7 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/BUILD.bazel @@ -0,0 +1,5 @@ +filegroup( + name = "files", + srcs = glob(["**/*"]), + visibility = ["//visibility:public"], +) diff --git a/adev/shared-docs/pipeline/examples/template/angular.json b/adev/shared-docs/pipeline/examples/template/angular.json new file mode 100644 index 000000000000..95567dd69a28 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/angular.json @@ -0,0 +1,109 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "analytics": false + }, + "newProjectRoot": "projects", + "projects": { + "example-app": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "css" + }, + "@schematics/angular:application": { + "strict": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/example-app", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "css", + "assets": ["src/assets"], + "styles": ["src/styles.css"], + "stylePreprocessorOptions": { + "includePaths": ["node_modules/"] + }, + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "example-app:build:production" + }, + "development": { + "browserTarget": "example-app:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "example-app:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "css", + "assets": ["src/assets"], + "stylePreprocessorOptions": { + "includePaths": ["node_modules/"] + }, + "styles": ["src/styles.css"], + "scripts": [] + } + } + } + } + } +} diff --git a/adev/shared-docs/pipeline/examples/template/karma.conf.js b/adev/shared-docs/pipeline/examples/template/karma.conf.js new file mode 100644 index 000000000000..c82069f17d49 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/karma.conf.js @@ -0,0 +1,41 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma'), + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false, // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true, // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/example-app'), + subdir: '.', + reporters: [{type: 'html'}, {type: 'text-summary'}], + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true, + }); +}; diff --git a/adev/shared-docs/pipeline/examples/template/package.json.template b/adev/shared-docs/pipeline/examples/template/package.json.template new file mode 100644 index 000000000000..e3a12391ad03 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/package.json.template @@ -0,0 +1,39 @@ +{ + "name": "example-app", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^16.1.1", + "@angular/common": "^16.1.1", + "@angular/compiler": "^16.1.1", + "@angular/core": "^16.1.1", + "@angular/forms": "^16.1.1", + "@angular/platform-browser": "^16.1.1", + "@angular/platform-browser-dynamic": "^16.1.1", + "@angular/router": "^16.1.1", + "rxjs": "~7.4.0", + "tslib": "^2.3.0", + "zone.js": "~0.13.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^16.1.0", + "@angular/cli": "^16.1.0", + "@angular/compiler-cli": "^16.1.1", + "@types/jasmine": "~3.10.0", + "@types/node": "^12.11.1", + "jasmine-core": "~3.10.0", + "karma": "~6.3.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.0.3", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~5.0.3" + } +} diff --git a/adev/shared-docs/pipeline/examples/template/src/app/app.component.ts b/adev/shared-docs/pipeline/examples/template/src/app/app.component.ts new file mode 100644 index 000000000000..dae28251948d --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/src/app/app.component.ts @@ -0,0 +1,23 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Component} from '@angular/core'; + +@Component({ + standalone: true, + selector: 'app-root', + template: '

Example template

', + styles: [ + ` + h2 { + color: blue; + } + `, + ], +}) +export class AppComponent {} diff --git a/adev/shared-docs/pipeline/examples/template/src/index.html b/adev/shared-docs/pipeline/examples/template/src/index.html new file mode 100644 index 000000000000..cb4be54ab556 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/src/index.html @@ -0,0 +1,14 @@ + + + + + Example + + + + + + + + + diff --git a/adev/shared-docs/pipeline/examples/template/src/main.ts b/adev/shared-docs/pipeline/examples/template/src/main.ts new file mode 100644 index 000000000000..fd552b11b051 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/src/main.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {bootstrapApplication, provideProtractorTestingSupport} from '@angular/platform-browser'; + +import {AppComponent} from './app/app.component'; + +bootstrapApplication(AppComponent, { + providers: [provideProtractorTestingSupport()], +}).catch((err) => console.error(err)); diff --git a/adev/shared-docs/pipeline/examples/template/src/styles.css b/adev/shared-docs/pipeline/examples/template/src/styles.css new file mode 100644 index 000000000000..d53300c2f24f --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/src/styles.css @@ -0,0 +1,9 @@ +body { + font-family: Roboto, 'Helvetica Neue', sans-serif; + margin: 0; + padding: 30px; +} + +html, body { + height: 100%; +} diff --git a/adev/shared-docs/pipeline/examples/template/tsconfig.app.json b/adev/shared-docs/pipeline/examples/template/tsconfig.app.json new file mode 100644 index 000000000000..f64f655d52d1 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/adev/shared-docs/pipeline/examples/template/tsconfig.json b/adev/shared-docs/pipeline/examples/template/tsconfig.json new file mode 100644 index 000000000000..ae207fd91856 --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/tsconfig.json @@ -0,0 +1,37 @@ +/* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "es2022", + "module": "es2020", + "lib": ["es2020", "dom"], + // Strictness flags. Matching the settings applied in the Angular Components source + // code, ensuring that examples do not break in StackBlitz with stricter settings. + "noUnusedParameters": false, + "noUnusedLocals": false, + "useDefineForClassFields": false, + "strictNullChecks": true, + "noImplicitReturns": true, + "strictFunctionTypes": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictBindCallApply": true, + "esModuleInterop": true + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/adev/shared-docs/pipeline/examples/template/tsconfig.spec.json b/adev/shared-docs/pipeline/examples/template/tsconfig.spec.json new file mode 100644 index 000000000000..ada6e4559def --- /dev/null +++ b/adev/shared-docs/pipeline/examples/template/tsconfig.spec.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["jasmine"] + }, + "files": ["src/test.ts"], + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/adev/shared-docs/pipeline/examples/zip/BUILD.bazel b/adev/shared-docs/pipeline/examples/zip/BUILD.bazel new file mode 100644 index 000000000000..9e280406ba4c --- /dev/null +++ b/adev/shared-docs/pipeline/examples/zip/BUILD.bazel @@ -0,0 +1,41 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "zip", + srcs = glob( + [ + "*.ts", + ], + exclude = [ + "index.ts", + ], + ), + deps = [ + "//adev/shared-docs/interfaces", + "//adev/shared-docs/pipeline/examples/shared", + "//adev/shared-docs/pipeline/guides", + "@npm//@types/node", + "@npm//fast-glob", + "@npm//fflate", + ], +) + +ts_library( + name = "index", + srcs = [ + "index.ts", + ], + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + ":zip", + "@npm//@types/node", + ], +) + +exports_files([ + "index.ts", +]) diff --git a/adev/shared-docs/pipeline/examples/zip/builder.ts b/adev/shared-docs/pipeline/examples/zip/builder.ts new file mode 100644 index 000000000000..06f26987874f --- /dev/null +++ b/adev/shared-docs/pipeline/examples/zip/builder.ts @@ -0,0 +1,112 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {join} from 'path'; +import {readFile} from 'fs/promises'; +import {copyFolder, createFolder} from '../shared/file-system'; +import {glob} from 'fast-glob'; +import {regionParser} from '../../guides/extensions/docs-code/regions/region-parser'; +import {appendCopyrightToFile} from '../shared/copyright'; +import {FileType} from '../../guides/extensions/docs-code/sanitizers/eslint'; +import {EXCLUDE_FILES, CONFIG_FILENAME} from './defaults'; +import {zip, strToU8} from 'fflate'; + +import {FileAndContent} from '../../../interfaces'; + +interface ZipConfig { + ignore: string[]; + files: string[]; +} + +export async function generateZipExample( + exampleDir: string, + workingDir: string, + templateDir: string, +) { + const config = await readFile(join(exampleDir, CONFIG_FILENAME), 'utf-8'); + const stackblitzConfig: ZipConfig = JSON.parse(config) as ZipConfig; + + await createFolder(workingDir); + + // Copy template files to TEMP folder + await copyFolder(templateDir, workingDir); + + // Copy example files to TEMP folder + await copyFolder(exampleDir, workingDir); + const includedPaths = await getIncludedPaths(workingDir, stackblitzConfig); + + const filesObj: Record = {}; + for (const path of includedPaths) { + const file = await getFileAndContent(workingDir, path); + filesObj[file.path] = typeof file.content === 'string' ? strToU8(file.content) : file.content; + } + + return new Promise((resolve, reject) => { + zip(filesObj, (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); +} + +async function getIncludedPaths(workingDir: string, config: ZipConfig): Promise { + const defaultIncludes = [ + '**/*.ts', + '**/*.js', + '**/*.css', + '**/*.html', + '**/*.md', + '**/*.json', + '**/*.svg', + ]; + return glob(defaultIncludes, { + cwd: workingDir, + onlyFiles: true, + dot: true, + ignore: config.ignore, + }); +} + +async function getFileAndContent(workingDir: string, path: string): Promise { + let content = await readFile(join(workingDir, path), 'utf-8'); + content = appendCopyrightToFile(path, content); + content = extractRegions(path, content); + + return {content, path}; +} + +async function createPostData( + exampleDir: string, + config: ZipConfig, + exampleFilePaths: string[], +): Promise> { + const postData: Record = {}; + + for (const filePath of exampleFilePaths) { + if (EXCLUDE_FILES.some((excludedFile) => filePath.endsWith(excludedFile))) { + continue; + } + + let content = await readFile(join(exampleDir, filePath), 'utf-8'); + content = appendCopyrightToFile(filePath, content); + content = extractRegions(filePath, content); + + postData[`project[files][${filePath}]`] = content; + } + + return postData; +} + +function extractRegions(path: string, contents: string): string { + const fileType: FileType | undefined = path?.split('.').pop() as FileType; + const regionParserResult = regionParser(contents, fileType); + return regionParserResult.contents; +} diff --git a/adev/shared-docs/pipeline/examples/zip/defaults.ts b/adev/shared-docs/pipeline/examples/zip/defaults.ts new file mode 100644 index 000000000000..f81a79a5ccde --- /dev/null +++ b/adev/shared-docs/pipeline/examples/zip/defaults.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export const TEST_FILES_EXTENSION_SUFFIX = '.spec.ts'; +export const TEST_FILES_E2E_EXTENSION_SUFFIX = '.e2e-spec.ts'; +export const BUILD_BAZEL_FILENAME = 'BUILD.bazel'; +export const EXAMPLE_CONFIG_FILENAME = 'example-config.json'; +export const CONFIG_FILENAME = 'zip.json'; + +/** Default file paths to be excluded from stackblitz examples. */ +export const EXCLUDE_FILES = [ + CONFIG_FILENAME, + BUILD_BAZEL_FILENAME, + EXAMPLE_CONFIG_FILENAME, + TEST_FILES_EXTENSION_SUFFIX, + TEST_FILES_E2E_EXTENSION_SUFFIX, +]; diff --git a/adev/shared-docs/pipeline/examples/zip/index.ts b/adev/shared-docs/pipeline/examples/zip/index.ts new file mode 100644 index 000000000000..a9878dab74ae --- /dev/null +++ b/adev/shared-docs/pipeline/examples/zip/index.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {writeFileSync} from 'fs'; +import {generateZipExample} from './builder'; + +const [exampleDir, tmpDir, templateDir, outputFilePath] = process.argv.slice(2); +const zipContent = await generateZipExample(exampleDir, tmpDir, templateDir); + +writeFileSync(outputFilePath, zipContent); diff --git a/adev/shared-docs/pipeline/guides/BUILD.bazel b/adev/shared-docs/pipeline/guides/BUILD.bazel new file mode 100644 index 000000000000..ebe677e85841 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/BUILD.bazel @@ -0,0 +1,47 @@ +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "guides", + srcs = glob( + [ + "**/*.ts", + ], + exclude = ["index.ts"], + ), + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + # "@npm//@angular/build-tooling/bazel/api-gen/rendering:entities", + "@npm//@bazel/runfiles", + "@npm//@types/diff", + "@npm//@types/jsdom", + "@npm//@types/node", + "@npm//diff", + "@npm//emoji-regex", + "@npm//html-entities", + "@npm//jsdom", + "@npm//marked", + "@npm//mermaid", + "@npm//playwright-core", + "@npm//shiki", + ], +) + +ts_library( + name = "index", + srcs = [ + "index.ts", + ], + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + ":guides", + "@npm//@types/node", + ], +) + +exports_files([ + "index.ts", +]) diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-alert.ts b/adev/shared-docs/pipeline/guides/extensions/docs-alert.ts new file mode 100644 index 000000000000..053e0d35d605 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-alert.ts @@ -0,0 +1,78 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {RendererThis, Token, TokenizerThis, Tokens} from 'marked'; + +/** Enum of all available alert severities. */ +export enum AlertSeverityLevel { + Note = 'NOTE', + Tip = 'TIP', + TODO = 'TODO', + QUESTION = 'QUESTION', + Summary = 'SUMMARY', + TLDR = 'TLDR', + CRITICAL = 'CRITICAL', + IMPORTANT = 'IMPORTANT', + HELPFUL = 'HELPFUL', +} + +/** Token for docs-alerts */ +interface DocsAlertToken extends Tokens.Generic { + type: 'docs-alert'; + body: string; + severityLevel: string; + tokens: Token[]; +} + +interface DocsAlert { + alert: RegExpExecArray | null; + severityLevel: string; +} + +export const docsAlertExtension = { + name: 'docs-alert', + level: 'block' as const, + tokenizer(this: TokenizerThis, src: string): DocsAlertToken | undefined { + let match: DocsAlert | undefined; + for (let level in AlertSeverityLevel) { + // Capture group 1: all alert text content after the severity level + const rule = new RegExp('^s*' + level + ': (.*?)\n(\n|$)', 's'); + const possibleMatch = rule.exec(src); + + if (possibleMatch?.[1]) { + match = { + severityLevel: level, + alert: possibleMatch, + }; + } + } + + if (match?.alert) { + const token: DocsAlertToken = { + type: 'docs-alert', + raw: match.alert[0], + body: match.alert[1].trim(), + severityLevel: match.severityLevel, + tokens: [], + }; + token.body = `**${ + token.severityLevel === AlertSeverityLevel.TLDR ? 'TL;DR' : token.severityLevel + }:** ${token.body}`; + this.lexer.blockTokens(token.body, token.tokens); + return token; + } + return undefined; + }, + renderer(this: RendererThis, token: DocsAlertToken) { + return ` +
+ ${this.parser.parse(token.tokens)} +
+ `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-callout.ts b/adev/shared-docs/pipeline/guides/extensions/docs-callout.ts new file mode 100644 index 000000000000..ad5f4d1d0de4 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-callout.ts @@ -0,0 +1,78 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Tokens, Token, TokenizerThis, RendererThis} from 'marked'; + +/** Enum of all available callout severities. */ +export enum CalloutSeverityLevel { + HELPFUL = 'HELPFUL', + IMPORTANT = 'IMPORTANT', + CRITICAL = 'CRITICAL', +} + +/** Token for docs-callouts */ +interface DocsCalloutToken extends Tokens.Generic { + type: 'docs-callout'; + title: string; + titleTokens: Token[]; + severityLevel: CalloutSeverityLevel; + body: string; + bodyTokens: Token[]; +} + +// Capture group 1: all attributes on the opening tag +// Capture group 2: all content between the open and close tags +const calloutRule = /^]*)>((?:.(?!\/docs-callout))*)<\/docs-callout>/s; + +const titleRule = /title="([^"]*)"/; +const isImportantRule = /important/; +const isCriticalRule = /critical/; + +export const docsCalloutExtension = { + name: 'docs-callout', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* +

${this.parser.parseInline(token.titleTokens)}

+ ${this.parser.parse(token.bodyTokens)} + + `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-card/docs-card-container.ts b/adev/shared-docs/pipeline/guides/extensions/docs-card/docs-card-container.ts new file mode 100644 index 000000000000..63134819332a --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-card/docs-card-container.ts @@ -0,0 +1,48 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Tokens, Token, RendererThis, TokenizerThis} from 'marked'; + +interface DocsCardContainerToken extends Tokens.Generic { + type: 'docs-card-container'; + cards: string; + tokens: Token[]; +} + +const cardContainerRule = /^(.*?)<\/docs-card-container>/s; + +export const docsCardContainerExtension = { + name: 'docs-card-container', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* + ${this.parser.parse(token.tokens)} + + `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-card/docs-card.ts b/adev/shared-docs/pipeline/guides/extensions/docs-card/docs-card.ts new file mode 100644 index 000000000000..104202f9ffc1 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-card/docs-card.ts @@ -0,0 +1,122 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Tokens, Token, RendererThis, TokenizerThis} from 'marked'; +import {anchorTarget} from '../../helpers'; +import {loadWorkspaceRelativeFile} from '../../utils'; + +interface DocsCardToken extends Tokens.Generic { + type: 'docs-card'; + title: string; + body: string; + link?: string; + href?: string; + imgSrc?: string; + tokens: Token[]; +} + +// Capture group 1: all attributes on the opening tag +// Capture group 2: all content between the open and close tags +const cardRule = /^[^<]*]*)>((?:.(?!\/docs-card))*)<\/docs-card>/s; + +const titleRule = /title="([^"]*)"/; +const linkRule = /link="([^"]*)"/; +const hrefRule = /href="([^"]*)"/; +const imgSrcRule = /imgSrc="([^"]*)"/; + +export const docsCardExtension = { + name: 'docs-card', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* +
+

${token.title}

+ ${renderer.parser.parse(token.tokens)} +
+ ${token.link ? token.link : 'Learn more'} + + `; + } + return ` +
+
+

${token.title}

+ ${renderer.parser.parse(token.tokens)} +
+ ${token.link ? `${token.link}` : ''} +
+ `; +} + +function getCardWithSvgIllustration(renderer: RendererThis, token: DocsCardToken) { + // We can assume that all illustrations are svg files + // We need to read svg content, instead of renering svg with `img`, + // cause we would like to use CSS variables to support dark and light mode. + const illustration = loadWorkspaceRelativeFile(token.imgSrc!); + + if (token.href) { + return ` + + ${illustration} +
+
+

${token.title}

+ ${renderer.parser.parse(token.tokens)} +
+ ${token.link ? token.link : 'Learn more'} +
+
+ `; + } + return ` +
+ ${illustration} +
+

${token.title}

+ ${renderer.parser.parse(token.tokens)} +
+
+ `; +} diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code-block.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code-block.ts new file mode 100644 index 000000000000..c380014f8d8b --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code-block.ts @@ -0,0 +1,53 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {TokenizerThis, RendererThis} from 'marked'; +import {CodeToken, formatCode} from './format/index'; + +export interface DocsCodeBlock extends CodeToken { + type: 'docs-code-block'; + // Nested code + code: string; + // Code language + language: string | undefined; +} + +// TODO: use regex for code implemented in the marked package: https://github.com/markedjs/marked/blob/4e6acc8b8517eafe0036a914f58b6f53d4b12ca6/src/rules.ts#L72C1-L73C1 +/** + * Regex for discovering code blocks, notably this is more limited than + * standard discovery of this as it only allows for exactly 3 ticks rather + * than three or more. + */ +const tripleTickCodeRule = /^\s*`{3}(\S+)[\r\n]+(.*?)[\r\n]+`{3}/s; + +export const docsCodeBlockExtension = { + name: 'docs-code-block', + level: 'block' as const, + start(src: string) { + return src.match(/^(```)\s/)?.index; + }, + tokenizer(this: TokenizerThis, src: string): DocsCodeBlock | undefined { + const match = tripleTickCodeRule.exec(src); + if (match) { + const token: DocsCodeBlock = { + raw: match[0], + type: 'docs-code-block', + code: match[2], + language: match[1], + }; + return token; + } + return undefined; + }, + renderer(this: RendererThis, token: DocsCodeBlock) { + if (token.language === 'mermaid') { + return token.code; + } + return formatCode(token); + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code-multifile.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code-multifile.ts new file mode 100644 index 000000000000..960e0e86081c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code-multifile.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Tokens, Token, TokenizerThis, RendererThis} from 'marked'; +import {JSDOM} from 'jsdom'; + +/** Marked token for a multifile custom docs element. */ +export interface DocsCodeMultifileToken extends Tokens.Generic { + type: 'docs-code-multifile'; + // The example path used for linking to Stackblitz or rendering a preview + path: string | undefined; + // The raw nested Markdown of examples in the multifile example + panes: string; + // The DocsCodeToken of the nested examples + paneTokens: Token[]; + // True if we should display preview + preview: boolean; +} + +// Capture group 1: all attributes on the opening tag +// Capture group 2: all content between the open and close tags +const multiFileCodeRule = /^\s*(.*?)<\/docs-code-multifile>/s; + +const pathRule = /path="([^"]*)"/; +const previewRule = /preview/; + +export const docsCodeMultifileExtension = { + name: 'docs-code-multifile', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* + ${this.parser.parse(token.paneTokens)} + + `).firstElementChild!; + + if (token.path) { + el.setAttribute('path', token.path); + } + if (token.preview) { + el.setAttribute('preview', `${token.preview}`); + } + + return el.outerHTML; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code.ts new file mode 100644 index 000000000000..d97710a9c2d9 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/docs-code.ts @@ -0,0 +1,87 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {TokenizerThis, RendererThis} from 'marked'; +import {CodeToken, formatCode} from './format/index'; +import {FileType, removeEslintComments} from './sanitizers/eslint'; +import {loadWorkspaceRelativeFile} from '../../utils'; + +/** Marked token for a custom docs element. */ +export interface DocsCodeToken extends CodeToken { + type: 'docs-code'; +} + +// Capture group 1: all attributes on the opening tag +// Capture group 2: all content between the open and close tags +const singleFileSelfClosingCodeRule = /^\s*]*)((?:.(?!\/>))*)\/>/s; +const singleFileCodeRule = /^\s*]*)>((?:.(?!\/docs-code))*)<\/docs-code>/s; + +const pathRule = /path="([^"]*)"/; +const headerRule = /header="([^"]*)"/; +const linenumsRule = /linenums/; +const highlightRule = /highlight="([^"]*)"/; +const diffRule = /diff="([^"]*)"/; +const languageRule = /language="([^"]*)"/; +const visibleLinesRule = /visibleLines="([^"]*)"/; +const visibleRegionRule = /visibleRegion="([^"]*)"/; +const previewRule = /preview/; + +export const docsCodeExtension = { + name: 'docs-code', + level: 'block' as const, + start(src: string) { + return src.match(/^ + Array.from(Array(count).keys()).map((i) => i + start); + + let processedLines = 0; + + token.diffMetadata = change.reduce( + (prev: DiffMetadata, part: DiffChange) => { + const diff: DiffMetadata = { + code: `${prev.code}${part.value}`, + linesAdded: part.added + ? [...prev.linesAdded, ...getLinesRange(processedLines, part.count ?? 0)] + : prev.linesAdded, + linesRemoved: part.removed + ? [...prev.linesRemoved, ...getLinesRange(processedLines, part.count ?? 0)] + : prev.linesRemoved, + }; + processedLines += part.count ?? 0; + return diff; + }, + { + code: '', + linesAdded: [], + linesRemoved: [], + }, + ); + token.code = token.diffMetadata.code; +} diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/format/highlight.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/highlight.ts new file mode 100644 index 000000000000..ab11d8477a7c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/highlight.ts @@ -0,0 +1,130 @@ +import {decode} from 'html-entities'; +import {CodeToken} from './index'; +import {expandRangeStringValues} from './range'; +import {JSDOM} from 'jsdom'; +import {createHighlighter, HighlighterGeneric} from 'shiki'; + +const lineNumberClassName: string = 'shiki-ln-number'; +const lineAddedClassName: string = 'add'; +const lineRemovedClassName: string = 'remove'; +const lineHighlightedClassName: string = 'highlighted'; + +let highlighter: HighlighterGeneric; + +/** + * Highlighter needs to setup asynchronously + * + * This is intended to be invoked at the start of the pipeline + */ +export async function initHighlighter() { + highlighter = await createHighlighter({ + themes: ['github-light', 'github-dark'], + langs: [ + 'javascript', + 'typescript', + 'angular-html', + 'angular-ts', + 'shell', + 'html', + 'http', + 'json', + 'nginx', + 'markdown', + 'apache', + ], + }); +} + +/** + * Updates the provided token's code value to include syntax highlighting. + */ +export function highlightCode(token: CodeToken) { + // TODO(josephperrott): Handle mermaid usages i.e. language == mermaidClassName + if (token.language !== 'none' && token.language !== 'file') { + // Decode the code content to replace HTML entities to characters + const decodedCode = decode(token.code); + const fallbackLanguage = token.path?.endsWith('html') ? 'angular-html' : 'angular-ts'; + const value = highlighter.codeToHtml(decodedCode, { + // we chose ts a fallback language as most example are ts. + // Idealy all examples should have a specified language + lang: token.language ?? fallbackLanguage, + themes: { + light: 'github-light', + dark: 'github-dark', + }, + cssVariablePrefix: '--shiki-', + defaultColor: false, + }); + token.code = value; + } + + const dom = new JSDOM(token.code); + const document = dom.window.document; + const lines = document.body.querySelectorAll('.line'); + + // removing whitespaces text nodes so we don't have spaces between codelines + removeWhitespaceNodes(document.body.querySelector('.shiki > code')); + + const linesCount = lines.length; + if (linesCount === 0) { + return; + } + + let lineIndex = 0; + let resultFileLineIndex = 1; + + const highlightedLineRanges = token.highlight ? expandRangeStringValues(token.highlight) : []; + + do { + const isRemovedLine = token.diffMetadata?.linesRemoved.includes(lineIndex); + const isAddedLine = token.diffMetadata?.linesAdded.includes(lineIndex); + const isHighlighted = highlightedLineRanges.includes(lineIndex); + const addClasses = (el: Element) => { + if (isRemovedLine) { + el.classList.add(lineRemovedClassName); + } + if (isAddedLine) { + el.classList.add(lineAddedClassName); + } + if (isHighlighted) { + el.classList.add(lineHighlightedClassName); + } + }; + + const currentline = lines[lineIndex]; + addClasses(currentline); + + if (!!token.linenums) { + const lineNumberEl = JSDOM.fragment( + ``, + ).firstElementChild!; + addClasses(lineNumberEl); + lineNumberEl.textContent = isRemovedLine ? '-' : isAddedLine ? '+' : `${resultFileLineIndex}`; + + currentline.parentElement!.insertBefore(lineNumberEl, currentline); + resultFileLineIndex++; + } + + lineIndex++; + } while (lineIndex < linesCount); + + token.code = document.body.innerHTML; +} + +/** + * + * Removed whitespaces between 1st level children + */ +function removeWhitespaceNodes(parent: Element | null) { + if (!parent) { + return; + } + + const childNodes = parent.childNodes; + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + if (node.nodeType === 3 && !/\S/.test(node.nodeValue!)) { + parent.removeChild(node); + } + } +} diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/format/index.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/index.ts new file mode 100644 index 000000000000..802daaed19ce --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/index.ts @@ -0,0 +1,92 @@ +import {Tokens} from 'marked'; +import {DiffMetadata, calculateDiff} from './diff'; +import {highlightCode} from './highlight'; +import {extractRegions} from './region'; +import {JSDOM} from 'jsdom'; +import {expandRangeStringValues} from './range'; + +/** Marked token for a custom docs element. */ +export interface CodeToken extends Tokens.Generic { + /* Nested code OR the code from the optional file path */ + code: string; + /* Code language */ + language: string | undefined; + + /* The example file path */ + path?: string; + /* The example display header */ + header?: string; + /* Whether stling should include line numbers */ + linenums?: boolean; + /* The example path to determine diff (lines added/removed) */ + diff?: string; + /* The lines viewable in collapsed view */ + visibleLines?: string; + /* The name of the viewable region in the collapsed view */ + visibleRegion?: string; + /* Whether we should display preview */ + preview?: boolean; + /* The lines to display highlighting on */ + highlight?: string; + + /** The generated diff metadata if created in the code formating process. */ + diffMetadata?: DiffMetadata; +} + +export function formatCode(token: CodeToken) { + if (token.visibleLines !== undefined && token.visibleRegion !== undefined) { + throw Error('Cannot define visible lines and visible region at the same time'); + } + + extractRegions(token); + calculateDiff(token); + highlightCode(token); + + const containerEl = JSDOM.fragment(` +
+ ${buildHeaderElement(token)} +
+      ${token.code}
+    
+
+ `).firstElementChild!; + + applyContainerAttributesAndClasses(containerEl, token); + + return containerEl.outerHTML; +} + +/** Build the header element if a header is provided in the token. */ +function buildHeaderElement(token: CodeToken) { + return token.header ? `

${token.header}

` : ''; +} + +function applyContainerAttributesAndClasses(el: Element, token: CodeToken) { + // Attributes + // String value attributes + if (token.diff) { + el.setAttribute('path', token.diff); + } + if (token.path) { + el.setAttribute('path', token.path); + } + if (token.visibleLines) { + el.setAttribute('visibleLines', expandRangeStringValues(token.visibleLines).toString()); + } + if (token.header) { + el.setAttribute('header', token.header); + } + + // Boolean value attributes + if (token.preview) { + el.setAttribute('preview', 'true'); + } + if (token.language === 'mermaid') { + el.setAttribute('mermaid', 'true'); + } + + // Classes + if (token.language === 'shell') { + el.classList.add('shell'); + } +} diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/format/range.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/range.ts new file mode 100644 index 000000000000..5730dedcd57c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/range.ts @@ -0,0 +1,46 @@ +/** + * Expand a provided set of range values into a singel array of all values in the range. + * + * For example, [[1,3], [12-13]] is expanded to [1,2,3,12,13]. + */ +export function expandRangeStringValues(rangeString: string | undefined): number[] { + if (rangeString === undefined) { + return []; + } + const getAllValuesFromRange = (range: any[]) => { + const [start, end] = range; + for (let i = start; i <= end; i++) { + result.push(i - 1); + } + }; + + let result: number[] = []; + try { + const boundaryValueArray = JSON.parse(rangeString) as any; + if (!Array.isArray(boundaryValueArray)) { + throw new Error('Provided token has wrong format!\n' /* boundaryValueArray */); + } + // Flat Array + if ( + boundaryValueArray.length === 2 && + !Array.isArray(boundaryValueArray[0]) && + !Array.isArray(boundaryValueArray[1]) + ) { + getAllValuesFromRange(boundaryValueArray); + } else { + for (const range of boundaryValueArray) { + if (Array.isArray(range) && range.length === 2) { + getAllValuesFromRange(range); + } else if (!Number.isNaN(range)) { + result.push(Number(range - 1)); + } else { + throw new Error('Input has wrong format!\n' /* range */); + } + } + } + + return result; + } catch { + return []; + } +} diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/format/region.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/region.ts new file mode 100644 index 000000000000..214e905071cc --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/format/region.ts @@ -0,0 +1,23 @@ +import {CodeToken} from './index'; +import {regionParser} from '../regions/region-parser'; +import {FileType} from '../sanitizers/eslint'; + +/** + * Updates the provided token to include the extracted region as the visible lines for the token. + */ +export function extractRegions(token: CodeToken) { + const fileType: FileType | undefined = token.path?.split('.').pop() as FileType; + const parsedRegions = regionParser(token.code, fileType); + // The code in the token is always replaced with the version of the code with region markers removed. + token.code = parsedRegions.contents; + + if (token.visibleRegion) { + const region = parsedRegions.regionMap[token.visibleRegion]; + if (!region) { + throw new Error(`Cannot find ${token.visibleRegion} in ${token.path}!`); + } + token.visibleLines = `[${region.ranges.map( + (range) => `[${range.from}, ${range.to ?? parsedRegions.totalLinesCount + 1}]`, + )}]`; + } +} diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/block-c.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/block-c.ts new file mode 100644 index 000000000000..056bbcfd0e87 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/block-c.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// These kind of comments are used CSS and other languages that do not support inline comments +export const regionStartMatcher = /^\s*\/\*\s*#docregion\s*(.*)\s*\*\/\s*$/; +export const regionEndMatcher = /^\s*\/\*\s*#enddocregion\s*(.*)\s*\*\/\s*$/; +export const plasterMatcher = /^\s*\/\*\s*#docplaster\s*(.*)\s*\*\/\s*$/; +export const createPlasterComment = (plaster: string) => `/* ${plaster} */`; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/html.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/html.ts new file mode 100644 index 000000000000..cffe30855686 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/html.ts @@ -0,0 +1,14 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// These kind of comments are used in HTML + +export const regionStartMatcher = /^\s*)?\s*$/; +export const regionEndMatcher = /^\s*\s*$/; +export const plasterMatcher = /^\s*\s*$/; +export const createPlasterComment = (plaster: string) => ``; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-c-only.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-c-only.ts new file mode 100644 index 000000000000..7ec8892020e2 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-c-only.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// These kind of comments are used in languages that do not support block comments, such as Jade +export const regionStartMatcher = /^\s*\/\/\s*#docregion\s*(.*)\s*$/; +export const regionEndMatcher = /^\s*\/\/\s*#enddocregion\s*(.*)\s*$/; +export const plasterMatcher = /^\s*\/\/\s*#docplaster\s*(.*)\s*$/; +export const createPlasterComment = (plaster: string) => `// ${plaster}`; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-c.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-c.ts new file mode 100644 index 000000000000..92dff390684e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-c.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// This comment type is used in C like languages such as JS, TS, etc +export const regionStartMatcher = /^\s*\/\/\s*#docregion\s*(.*)\s*$/; +export const regionEndMatcher = /^\s*\/\/\s*#enddocregion\s*(.*)\s*$/; +export const plasterMatcher = /^\s*\/\/\s*#docplaster\s*(.*)\s*$/; +export const createPlasterComment = (plaster: string) => `/* ${plaster} */`; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-hash.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-hash.ts new file mode 100644 index 000000000000..939539ce73e1 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-matchers/inline-hash.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// These type of comments are used in hash comment based languages such as bash and Yaml +export const regionStartMatcher = /^\s*#\s*#docregion\s*(.*)\s*$/; +export const regionEndMatcher = /^\s*#\s*#enddocregion\s*(.*)\s*$/; +export const plasterMatcher = /^\s*#\s*#docplaster\s*(.*)\s*$/; +export const createPlasterComment = (plaster: string) => `# ${plaster}`; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-parser.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-parser.ts new file mode 100644 index 000000000000..c6e4c32e705a --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/regions/region-parser.ts @@ -0,0 +1,203 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// Region parsing is done within this pipeline to allow for retrieving a subset of a file +// by extracting only the portion of the file between the provided regionblock comments. +// This also allows for documentation to be included within the source code more easily +// in an extractable manner. + +import * as blockC from './region-matchers/block-c'; +import * as html from './region-matchers/html'; +import * as inlineC from './region-matchers/inline-c'; +import * as inlineCOnly from './region-matchers/inline-c-only'; +import * as inlineHash from './region-matchers/inline-hash'; + +const WHOLE_FILE_REGION_NAME = ''; +const DEFAULT_PLASTER = '. . .'; +const REGION_MATCHERS = { + ts: inlineC, + js: inlineC, + mjs: inlineCOnly, + es6: inlineC, + html: html, + svg: html, + css: blockC, + conf: inlineHash, + yaml: inlineHash, + yml: inlineHash, + sh: inlineHash, + jade: inlineCOnly, + pug: inlineCOnly, + json: inlineCOnly, + 'json.annotated': inlineCOnly, +}; + +interface Region { + lines: string[]; + open?: boolean; + ranges: {from: number; to?: number}[]; +} + +type FileType = + | 'ts' + | 'js' + | 'mjs' + | 'es6' + | 'html' + | 'svg' + | 'css' + | 'conf' + | 'yaml' + | 'yml' + | 'sh' + | 'jade' + | 'pug' + | 'json' + | 'json.annotated'; + +/** + * NOTE: We assume that the tag defining the beginning and end of the region will be in different lines in each case. + * For example, in HTML, we don't expect to have the following code on one line: content + */ +export const regionParser = (contents: string, fileType: FileType) => { + const regionMatcher = REGION_MATCHERS[fileType]; + const openRegions: string[] = []; + const regionMap: Record = {}; + + let countOfRegionLines = 0; + + if (regionMatcher) { + let plaster = regionMatcher.createPlasterComment(DEFAULT_PLASTER); + const lines = contents.split(/\r?\n/).filter((line, index) => { + const startRegion = line.match(regionMatcher.regionStartMatcher); + const endRegion = line.match(regionMatcher.regionEndMatcher); + const updatePlaster = line.match(regionMatcher.plasterMatcher); + + // start region processing + if (startRegion) { + // open up the specified region + handleStartRegion(startRegion, regionMap, index, countOfRegionLines, plaster, openRegions); + } else if (endRegion) { + // end region processing + handleEndRegion(openRegions, endRegion, regionMap, index, countOfRegionLines); + } else if (updatePlaster) { + // doc plaster processing + const plasterString = updatePlaster[1].trim(); + plaster = plasterString ? regionMatcher.createPlasterComment(plasterString) : ''; + } else { + // simple line of content processing + openRegions.forEach((regionName) => regionMap[regionName].lines.push(line)); + // do not filter out this line from the content + return true; + } + + // this line contained an annotation so let's filter it out + countOfRegionLines++; + return false; + }); + + if (!regionMap[WHOLE_FILE_REGION_NAME]) { + regionMap[WHOLE_FILE_REGION_NAME] = {lines, ranges: [{from: 1, to: lines.length + 1}]}; + } + + return { + contents: lines.join('\n'), + regionMap, + totalLinesCount: lines.length, + }; + } else { + return {contents, regionMap, totalLinesCount: 0}; + } +}; + +function handleStartRegion( + startRegion: RegExpMatchArray, + regionMap: Record, + index: number, + countOfRegionLines: number, + plaster: string, + openRegions: string[], +) { + const regionNames = getRegionNames(startRegion[1]); + if (regionNames.length === 0) { + regionNames.push(WHOLE_FILE_REGION_NAME); + } + + for (const regionName of regionNames) { + const region = regionMap[regionName]; + if (region) { + if (region.open) { + throw new Error(`Tried to open a region, named "${regionName}", that is already open`); + } + + // Region is opened, set from range value. + region.open = true; + region.ranges.push({from: getFromRangeValue(index, countOfRegionLines)}); + + if (plaster) { + // Use the same indent as the docregion marker + const indent = startRegion[0].split(/[^ ]/, 1); + region.lines.push(indent + plaster); + } + } else { + regionMap[regionName] = { + lines: [], + open: true, + ranges: [ + { + from: getFromRangeValue(index, countOfRegionLines), + }, + ], + }; + } + openRegions.push(regionName); + } +} + +function handleEndRegion( + openRegions: string[], + endRegion: RegExpMatchArray, + regionMap: Record, + index: number, + countOfRegionLines: number, +) { + if (openRegions.length === 0) { + throw new Error('Tried to close a region when none are open'); + } + // close down the specified region (or most recent if no name is given) + const regionNames = getRegionNames(endRegion[1]); + if (regionNames.length === 0) { + regionNames.push(openRegions[openRegions.length - 1]); + } + + for (const regionName of regionNames) { + const region = regionMap[regionName]; + if (!region || !region.open) { + throw new Error(`Tried to close a region, named "${regionName}", that is not open`); + } + + // Region is closed, we can define the last line number of the region + region.open = false; + region.ranges[region.ranges.length - 1].to = index - countOfRegionLines; + removeLast(openRegions, regionName); + } +} + +function getFromRangeValue(index: number, countOfRegionLines: number): number { + return index - countOfRegionLines + 1; +} + +function getRegionNames(input: string): string[] { + return input.trim() === '' ? [] : input.split(',').map((name) => name.trim()); +} + +/** Remove last instance of the provided item from the array. */ +function removeLast(array: string[], item: string): void { + const index = array.lastIndexOf(item); + array.splice(index, 1); +} diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-code/sanitizers/eslint.ts b/adev/shared-docs/pipeline/guides/extensions/docs-code/sanitizers/eslint.ts new file mode 100644 index 000000000000..01a5b5218c86 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-code/sanitizers/eslint.ts @@ -0,0 +1,61 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export type FileType = + | 'ts' + | 'js' + | 'mjs' + | 'es6' + | 'html' + | 'svg' + | 'css' + | 'conf' + | 'yaml' + | 'yml' + | 'sh' + | 'jade' + | 'pug' + | 'json' + | 'json.annotated'; + +/** + * Remove any ESLint comments from the provided code string. This is particularly useful as we may + * want to store code formatted for an example that would not be allowed in our repo. For instance, + * we can disable ESLint to allow for a `console` usage, despite not allowing it typically, so that + * it can be used in an example in documenation. + */ +export function removeEslintComments(input: string, fileType: FileType) { + if (!input || (fileType !== 'ts' && fileType !== 'js' && fileType !== 'html')) { + return input; + } + return input.replace(regexesForFileTypes[fileType], ''); +} + +const jsRegexes = [ + /\/\/ *eslint-disable(?:-next-line)?(?: .*)?(?:\n *|$)/, + /\n? *\/\/ *eslint-(?:disable-line|enable)(?: .*)?(?=\n|$)/, + /\/\*\s*eslint-disable(?:-next-line)?(?: [\s\S]*?)?\*\/ *(?:\n *)?/, + /\n? *\/\*\s*eslint-(?:disable-line|enable)(?: [\s\S]*?)?\*\//, +]; + +const htmlRegexes = [ + / *(?:\n *)?/, + /\n? */, +]; + +const joinRegexes = (regexes: any) => + new RegExp(regexes.map((regex: any) => `(?:${regex.source})`).join('|'), 'g'); +const htmlRegex = joinRegexes(htmlRegexes); +// Note: the js regex needs to also include the html ones to account for inline templates in @Components +const jsRegex = joinRegexes([...jsRegexes, ...htmlRegexes]); + +const regexesForFileTypes = { + js: jsRegex, + ts: jsRegex, + html: htmlRegex, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-decorative-header.ts b/adev/shared-docs/pipeline/guides/extensions/docs-decorative-header.ts new file mode 100644 index 000000000000..7d209d1ddf60 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-decorative-header.ts @@ -0,0 +1,77 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {TokenizerThis, Tokens, RendererThis} from 'marked'; +import {getPageTitle, loadWorkspaceRelativeFile} from '../utils'; + +interface DocsDecorativeHeaderToken extends Tokens.Generic { + type: 'docs-decorative-header'; + title: string; + imgSrc: string; + body: string; +} + +// Capture group 1: all attributes on the opening tag +// Capture group 2: all content between the open and close tags +const decorativeHeaderRule = + /^[^<]*]*)>((?:.(?!\/docs-decorative-header))*)<\/docs-decorative-header>/s; + +const imgSrcRule = /imgSrc="([^"]*)"/; +const titleRule = /title="([^"]*)"/; + +export const docsDecorativeHeaderExtension = { + name: 'docs-decorative-header', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* +
+
+ + + ${getPageTitle(token.title)} + +

${token.body}

+
+ + + ${illustration} +
+ + `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-pill/docs-pill-row.ts b/adev/shared-docs/pipeline/guides/extensions/docs-pill/docs-pill-row.ts new file mode 100644 index 000000000000..4e66dc082692 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-pill/docs-pill-row.ts @@ -0,0 +1,49 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Token, Tokens, RendererThis, TokenizerThis} from 'marked'; + +interface DocsPillRowToken extends Tokens.Generic { + type: 'docs-pill-row'; + pills: string; + tokens: Token[]; +} + +// Capture group 1: all content between the open and close tags +const pillRowRule = /^\s*((?:.(?!docs-pill-row))*)<\/docs-pill-row>/s; + +export const docsPillRowExtension = { + name: 'docs-pill-row', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* + ${this.parser.parseInline(token.tokens)} + + `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-pill/docs-pill.ts b/adev/shared-docs/pipeline/guides/extensions/docs-pill/docs-pill.ts new file mode 100644 index 000000000000..42ee838e09dd --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-pill/docs-pill.ts @@ -0,0 +1,62 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Token, Tokens, RendererThis, TokenizerThis} from 'marked'; +import {anchorTarget, isExternalLink} from '../../helpers'; + +interface DocsPillToken extends Tokens.Generic { + type: 'docs-pill'; + title: string; + href: string; + tokens: Token[]; +} + +// Capture group 1: all attributes on the docs-pill tag +const pillRule = /^\s*/s; + +const titleRule = /title="([^"]*)"/; +const hrefRule = /href="([^"]*)"/; + +export const docsPillExtension = { + name: 'docs-pill', + level: 'inline' as const, + start(src: string) { + return src.indexOf(' + ${this.parser.parseInline(token.tokens)}${ + isExternalLink(token.href) + ? 'open_in_new' + : '' + } + + `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-video.ts b/adev/shared-docs/pipeline/guides/extensions/docs-video.ts new file mode 100644 index 000000000000..610f06123268 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-video.ts @@ -0,0 +1,70 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Tokens, TokenizerThis, RendererThis} from 'marked'; + +interface DocsVideoToken extends Tokens.Generic { + type: 'docs-video'; + src: string; + title: string | undefined; +} + +// Capture group 1: all attributes on the opening tag +const videoRule = /^]*)\/>/s; + +const srcRule = /src="([^"]*)"/; +const titleRule = /title="([^"]*)"/; +const validYTUrlPrefix = 'https://www.youtube.com/embed/'; + +export const docsVideoExtension = { + name: 'docs-video', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* cannot load: ${token.src}. YouTube Player API expects src to begin with ${validYTUrlPrefix}.\n`, + ); + } + + return ` +
+ +
+ `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-workflow/docs-step.ts b/adev/shared-docs/pipeline/guides/extensions/docs-workflow/docs-step.ts new file mode 100644 index 000000000000..efd64d6eef8c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-workflow/docs-step.ts @@ -0,0 +1,59 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Token, Tokens, RendererThis, TokenizerThis} from 'marked'; +import {headingRender} from '../../tranformations/heading'; + +interface DocsStepToken extends Tokens.Generic { + type: 'docs-step'; + title: string; + body: string; + tokens: Token[]; +} + +// Capture group 1: all attributes on the opening tag +// Capture group 2: all content between the open and close tags +const stepRule = /^\s*]*)>((?:.(?!\/docs-step))*)<\/docs-step>/s; +const titleRule = /title="([^"]*)"/; + +export const docsStepExtension = { + name: 'docs-step', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* + + ${headingRender(token.title, 3, token.title)} + ${this.parser.parse(token.tokens)} + + `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/extensions/docs-workflow/docs-workflow.ts b/adev/shared-docs/pipeline/guides/extensions/docs-workflow/docs-workflow.ts new file mode 100644 index 000000000000..a6be14022b4f --- /dev/null +++ b/adev/shared-docs/pipeline/guides/extensions/docs-workflow/docs-workflow.ts @@ -0,0 +1,50 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Token, Tokens, RendererThis, TokenizerThis} from 'marked'; + +interface DocsWorkflowToken extends Tokens.Generic { + type: 'docs-workflow'; + steps: string; + tokens: Token[]; +} + +// Capture group 1: all content between the open and close tags +const workflowRule = /^(.*?)<\/docs-workflow>/s; + +export const docsWorkflowExtension = { + name: 'docs-workflow', + level: 'block' as const, + start(src: string) { + return src.match(/^\s* + ${this.parser.parse(token.tokens)} + + `; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/helpers.ts b/adev/shared-docs/pipeline/guides/helpers.ts new file mode 100644 index 000000000000..a7b4fdb4963a --- /dev/null +++ b/adev/shared-docs/pipeline/guides/helpers.ts @@ -0,0 +1,17 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/** Whether the link provided is external to the application. */ +export function isExternalLink(href: string | undefined | null) { + return href?.startsWith('http') ?? false; +} + +/** Provide the correct target for the anchor tag based on the link provided. */ +export function anchorTarget(href: string | undefined | null) { + return isExternalLink(href) ? ` target="_blank"` : ''; +} diff --git a/adev/shared-docs/pipeline/guides/hooks.ts b/adev/shared-docs/pipeline/guides/hooks.ts new file mode 100644 index 000000000000..a5a2b967a816 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/hooks.ts @@ -0,0 +1,23 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {MarkedExtension} from 'marked'; +import {resetHeaderIdsOfCurrentDocument} from './state'; + +/** + * Custom hooks for marked that will be used to post-transform markdown files with parent styles for docs. + */ +export const hooks: MarkedExtension['hooks'] = { + preprocess(html: string): string { + resetHeaderIdsOfCurrentDocument(); + return html; + }, + postprocess(html: string): string { + return html; + }, +}; diff --git a/adev/shared-docs/pipeline/guides/index.d.ts b/adev/shared-docs/pipeline/guides/index.d.ts new file mode 100644 index 000000000000..773a9d3bdb10 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/index.d.ts @@ -0,0 +1,12 @@ +// This definition file is a temporary workaround until the toolchain is able to read d.mts definition files +// TODO: delete this file when the toolchains supports .d.mts files +declare module 'shiki' { + function createHighlighter(params: { + themes: any[]; + langs: string[]; + cssVariablePrefix?: string; + defaultColor?: boolean; + }): unknown; + + type HighlighterGeneric = any; +} diff --git a/adev/shared-docs/pipeline/guides/index.ts b/adev/shared-docs/pipeline/guides/index.ts new file mode 100644 index 000000000000..effa5057ca42 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/index.ts @@ -0,0 +1,39 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {readFileSync, writeFileSync} from 'fs'; +import path from 'path'; +import {parseMarkdown} from './parse'; +import {initHighlighter} from './extensions/docs-code/format/highlight'; + +async function main() { + const [paramFilePath] = process.argv.slice(2); + const rawParamLines = readFileSync(paramFilePath, {encoding: 'utf8'}).split('\n'); + const [srcs, outputFilenameExecRootRelativePath] = rawParamLines; + + // The highlighter needs to be setup asynchronously + // so we're doing it at the start of the pipeline + await initHighlighter(); + + for (const filePath of srcs.split(',')) { + if (!filePath.endsWith('.md')) { + throw new Error(`Input file "${filePath}" does not end in a ".md" file extension.`); + } + + const markdownContent = readFileSync(filePath, {encoding: 'utf8'}); + const htmlOutputContent = await parseMarkdown(markdownContent, {markdownFilePath: filePath}); + + // The expected file name structure is the [name of the file].md.html. + const htmlFileName = filePath + '.html'; + const htmlOutputPath = path.join(outputFilenameExecRootRelativePath, htmlFileName); + + writeFileSync(htmlOutputPath, htmlOutputContent, {encoding: 'utf8'}); + } +} + +main(); diff --git a/adev/shared-docs/pipeline/guides/mermaid/index.ts b/adev/shared-docs/pipeline/guides/mermaid/index.ts new file mode 100644 index 000000000000..99dda3c376a9 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/mermaid/index.ts @@ -0,0 +1,71 @@ +import {DocsCodeToken} from '../extensions/docs-code/docs-code'; +import {chromium} from 'playwright-core'; +import {Mermaid, MermaidConfig} from 'mermaid'; +import {runfiles} from '@bazel/runfiles'; + +// Declare mermarid in the context of this file so that typescript doesn't get upset when we +// access it within the `page.evaluate` function. At runtime the context in with the method +// is run difference than this file, but this makes typescript happy. +declare const mermaid: Mermaid; + +/** Mermaid configuration to use when creating mermaid svgs. */ +const mermaidConfig: MermaidConfig = { + // The `base` theme is the only configurable theme provided by mermaid. + theme: 'base', +}; + +/** The full path to the mermaid script. */ +let mermaidScriptTagData: {path: string} | undefined; + +/** Get the mermaid script file path, resolving it if necessary first. */ +function getMermaidScriptTagData() { + if (mermaidScriptTagData) { + return mermaidScriptTagData; + } + + return (mermaidScriptTagData = { + path: runfiles.resolveWorkspaceRelative('node_modules/mermaid/dist/mermaid.js'), + }); +} + +/** Replace the code block content with the mermaid generated SVG element string in place. */ +export async function processMermaidCodeBlock(token: DocsCodeToken) { + /** + * The diagram source code contents. Marked reuses the token object, causing the need for + * extracting the value before async actions occur in the function. + */ + const diagram = token.code; + // TODO(josephperrott): Determine if we can reuse the browser across token processing. + /** Browser instance to run mermaid within. */ + const browser = await chromium.launch({ + headless: true, + // The browser binary needs to be discoverable in a build and test environment, which seems to only + // work when provided at the execroot path. We choose to resolve it using the runfiles helper due + // to this requirement. + executablePath: runfiles.resolveWorkspaceRelative(process.env['CHROME_BIN']!), + args: ['--no-sandbox'], + }); + /** Page to run mermaid in. */ + const page = await browser.newPage(); + + try { + // We goto a data URI so that we don't have to manage an html file and loading an html file. + await page.goto(`data:text/html,`); + await page.addScriptTag(getMermaidScriptTagData()); + + /** The generated SVG element string for the provided token's code. */ + let {svg} = await page.evaluate( + ({diagram, config}) => { + mermaid.initialize(config); + + return mermaid.render('mermaid-generated-diagram', diagram); + }, + {diagram, config: mermaidConfig}, + ); + + // Replace the token's code content with the generated SVG. + token.code = svg; + } finally { + await browser.close(); + } +} diff --git a/adev/shared-docs/pipeline/guides/parse.ts b/adev/shared-docs/pipeline/guides/parse.ts new file mode 100644 index 000000000000..ac377f34060d --- /dev/null +++ b/adev/shared-docs/pipeline/guides/parse.ts @@ -0,0 +1,59 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {marked} from 'marked'; +import {hooks} from './hooks'; +import {renderer} from './renderer'; +import {docsAlertExtension} from './extensions/docs-alert'; +import {docsCalloutExtension} from './extensions/docs-callout'; +import {docsPillExtension} from './extensions/docs-pill/docs-pill'; +import {docsPillRowExtension} from './extensions/docs-pill/docs-pill-row'; +import {docsVideoExtension} from './extensions/docs-video'; +import {docsWorkflowExtension} from './extensions/docs-workflow/docs-workflow'; +import {docsStepExtension} from './extensions/docs-workflow/docs-step'; +import {docsCardExtension} from './extensions/docs-card/docs-card'; +import {docsCardContainerExtension} from './extensions/docs-card/docs-card-container'; +import {docsDecorativeHeaderExtension} from './extensions/docs-decorative-header'; +import {docsCodeBlockExtension} from './extensions/docs-code/docs-code-block'; +import {docsCodeExtension} from './extensions/docs-code/docs-code'; +import {docsCodeMultifileExtension} from './extensions/docs-code/docs-code-multifile'; +import {ParserContext, setContext} from './utils'; +import {walkTokens} from './walk-tokens'; + +export async function parseMarkdown( + markdownContent: string, + context: ParserContext, +): Promise { + setContext(context); + + marked.use({ + hooks, + renderer, + extensions: [ + docsAlertExtension, + docsCalloutExtension, + docsPillExtension, + docsPillRowExtension, + docsVideoExtension, + docsWorkflowExtension, + docsStepExtension, + docsCardExtension, + docsCardContainerExtension, + docsDecorativeHeaderExtension, + docsCodeBlockExtension, + docsCodeExtension, + docsCodeMultifileExtension, + ], + walkTokens, + // The async option causes marked to await walkTokens functions before parsing the tokens and returning an HTML string. + // We leverage this to allow us to use async libraries like mermaid and building stackblitz examples. + async: true, + }); + + return marked.parse(markdownContent); +} diff --git a/adev/shared-docs/pipeline/guides/renderer.ts b/adev/shared-docs/pipeline/guides/renderer.ts new file mode 100644 index 000000000000..8fe1379f80d7 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/renderer.ts @@ -0,0 +1,20 @@ +import {RendererObject} from 'marked'; +import {linkRender} from './tranformations/link'; +import {tableRender} from './tranformations/table'; +import {listRender} from './tranformations/list'; +import {imageRender} from './tranformations/image'; +import {textRender} from './tranformations/text'; +import {headingRender} from './tranformations/heading'; + +/** + * Custom renderer for marked that will be used to transform markdown files to HTML + * files that can be used in the Angular docs. + */ +export const renderer: RendererObject = { + link: linkRender, + table: tableRender, + list: listRender, + image: imageRender, + text: textRender, + heading: headingRender, +}; diff --git a/adev/shared-docs/pipeline/guides/state.ts b/adev/shared-docs/pipeline/guides/state.ts new file mode 100644 index 000000000000..988f8004d95a --- /dev/null +++ b/adev/shared-docs/pipeline/guides/state.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +const headerIds = new Map(); + +export const getHeaderId = (heading: string): string => { + const numberOfHeaderOccurrencesInTheDocument = headerIds.get(heading) ?? 0; + headerIds.set(heading, numberOfHeaderOccurrencesInTheDocument + 1); + + // extract the extended markdown heading id + // ex: ## MyHeading {# myId} + const match = heading.match(/{#([\w-]+)}/); + + let extractedId: string; + if (match) { + extractedId = match[1]; + } else { + extractedId = heading + .toLowerCase() + .replace(/(.*?)<\/code>/g, '$1') // remove + .replace(/(.*?)<\/strong>/g, '$1') // remove + .replace(/(.*?)<\/em>/g, '$1') // remove + .replace(/\s|\//g, '-') // remove spaces and slashes + .replace(/gt;|lt;/g, '') // remove escaped < and > + .replace(/&#\d+;/g, '') // remove HTML entities + .replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes + } + + const headerId = numberOfHeaderOccurrencesInTheDocument + ? `${extractedId}-${numberOfHeaderOccurrencesInTheDocument}` + : extractedId; + + return headerId; +}; + +export const resetHeaderIdsOfCurrentDocument = (): void => { + headerIds.clear(); +}; diff --git a/adev/shared-docs/pipeline/guides/testing/BUILD.bazel b/adev/shared-docs/pipeline/guides/testing/BUILD.bazel new file mode 100644 index 000000000000..3db4cfa1646e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/BUILD.bazel @@ -0,0 +1,38 @@ +load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") + +ts_library( + name = "unit_test_lib", + testonly = True, + srcs = glob([ + "**/*.spec.ts", + ]), + deps = [ + "//adev/shared-docs/pipeline/guides", + "@npm//@bazel/runfiles", + "@npm//@types/jsdom", + "@npm//jsdom", + ], +) + +ts_library( + name = "bootstrap", + testonly = True, + srcs = [ + "bootstrap.init.ts", + ], +) + +jasmine_node_test( + name = "unit_tests", + bootstrap = [ + ":bootstrap", + ], + data = [ + "@npm//jsdom", + ] + glob([ + "**/*.md", + "**/*.svg", + "**/*.ts", + ]), + deps = [":unit_test_lib"], +) diff --git a/adev/shared-docs/pipeline/guides/testing/bootstrap.init.ts b/adev/shared-docs/pipeline/guides/testing/bootstrap.init.ts new file mode 100644 index 000000000000..546d8e4052ef --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/bootstrap.init.ts @@ -0,0 +1,2 @@ +// Set HANDLE_MERMAID to false as unit testing does not handle mermaid. +(global as any).HANDLE_MERMAID = false; diff --git a/adev/shared-docs/pipeline/guides/testing/docs-alert/docs-alert.md b/adev/shared-docs/pipeline/guides/testing/docs-alert/docs-alert.md new file mode 100644 index 000000000000..56ea9fa35e2f --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-alert/docs-alert.md @@ -0,0 +1,18 @@ +Note: Use Note for ancillary/additional information that's not _essential_ to the main text. +This is a multiline note + +Tip: Use Tip to call out a specific task/action users can perform, or a fact that plays directly into a task/action. + +TODO: Use TODO for incomplete documentation that you plan to expand soon. You can also assign the TODO, e.g. TODO(emmatwersky): Text. + +QUESTION: Use Question to pose a question to the reader, kind of like a mini-quiz that they should be able to answer. + +Summary: Use Summary to provide a two- or three-sentence synopsis of the page or section content, so readers can figure out whether this is the right place for them. + +TLDR: Use TLDR if you can provide the essential information about a page or section in a sentence or two. + +CRITICAL: Use Critical to call out potential bad stuff or alert the reader they ought to be careful before doing something. For example, Warning: Running `rm` with the `-f` option will delete write-protected files or directories without prompting you. + +IMPORTANT: Use Important for information that's crucial to comprehending the text or to completing some task. + +HELPFUL: Use Best practice to call out practices that are known to be successful or better than alternatives. diff --git a/adev/shared-docs/pipeline/guides/testing/docs-alert/docs-alert.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-alert/docs-alert.spec.ts new file mode 100644 index 000000000000..504081e5a142 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-alert/docs-alert.spec.ts @@ -0,0 +1,36 @@ +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +import {AlertSeverityLevel} from '../../../guides/extensions/docs-alert'; +import {parseMarkdown} from '../../../guides/parse'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-alert/docs-alert.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + for (let level in AlertSeverityLevel) { + it(`should create a docs-alert for ${level}:`, () => { + const noteEl = markdownDocument.querySelector(`.docs-alert-${level.toLowerCase()}`); + // TLDR is written without a semi colon in the markdown, but is rendered + // with a colon, as such we have to adjust our expectation here. + if (level === AlertSeverityLevel.TLDR) { + level = 'TL;DR'; + } + expect(noteEl?.textContent?.trim()).toMatch(`^${level}:`); + }); + } + + it(`should handle multi-line alerts`, () => { + const noteEl = markdownDocument.querySelector(`.docs-alert-note`); + + expect(noteEl?.textContent?.trim()).toContain(`This is a multiline note`); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-callout/docs-callout.md b/adev/shared-docs/pipeline/guides/testing/docs-callout/docs-callout.md new file mode 100644 index 000000000000..c329eb6298d3 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-callout/docs-callout.md @@ -0,0 +1,16 @@ + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-callout/docs-callout.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-callout/docs-callout.spec.ts new file mode 100644 index 000000000000..bc325f42d3ab --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-callout/docs-callout.spec.ts @@ -0,0 +1,22 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-callout/docs-callout.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it(`defaults to a helpful callout`, () => { + const calloutDiv = + markdownDocument.querySelector('#default-marker')!.parentElement?.parentElement; + calloutDiv?.classList.contains('docs-callout-helpful'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-card-container/angular.svg b/adev/shared-docs/pipeline/guides/testing/docs-card-container/angular.svg new file mode 100644 index 000000000000..57d353b2643c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-card-container/angular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-card-container/docs-card-container.md b/adev/shared-docs/pipeline/guides/testing/docs-card-container/docs-card-container.md new file mode 100644 index 000000000000..b2cc295f7e41 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-card-container/docs-card-container.md @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-card-container/docs-card-container.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-card-container/docs-card-container.spec.ts new file mode 100644 index 000000000000..1263ec531a10 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-card-container/docs-card-container.spec.ts @@ -0,0 +1,23 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-card-container/docs-card-container.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('creates card containers containing multiple cards', () => { + const containerEl = markdownDocument.querySelector('.docs-card-grid'); + + expect(containerEl!.children.length).toBe(2); + expect(containerEl!.classList.contains('docs-card-grid')).toBeTrue(); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-card/angular.svg b/adev/shared-docs/pipeline/guides/testing/docs-card/angular.svg new file mode 100644 index 000000000000..57d353b2643c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-card/angular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-card/docs-card.md b/adev/shared-docs/pipeline/guides/testing/docs-card/docs-card.md new file mode 100644 index 000000000000..3752044a5281 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-card/docs-card.md @@ -0,0 +1,5 @@ +Card Content + + Card Content + + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-card/docs-card.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-card/docs-card.spec.ts new file mode 100644 index 000000000000..93c5522d9ec6 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-card/docs-card.spec.ts @@ -0,0 +1,39 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-card/docs-card.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('creates cards with no links', () => { + const cardEl = markdownDocument.querySelectorAll('.docs-card')[0]; + + expect(cardEl.querySelector('h3')?.textContent?.trim()).toBe('No Link Card'); + expect(cardEl.tagName).not.toBe('A'); + }); + + it('creates cards withs links', () => { + const cardEl = markdownDocument.querySelectorAll('.docs-card')[1]; + + expect(cardEl.querySelector('h3')?.textContent?.trim()).toBe('Link Card'); + expect(cardEl.tagName).toBe('A'); + + expect(cardEl.getAttribute('href')).toBe('in/app/link'); + }); + + it('creates cards with svg images', () => { + const cardEl = markdownDocument.querySelectorAll('.docs-card')[2]; + + expect(cardEl.querySelector('h3')?.textContent?.trim()).toBe('Image Card'); + expect(cardEl.querySelector('svg')).toBeTruthy(); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code-block/docs-code-block.md b/adev/shared-docs/pipeline/guides/testing/docs-code-block/docs-code-block.md new file mode 100644 index 000000000000..6896608fd142 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code-block/docs-code-block.md @@ -0,0 +1,3 @@ +```typescript +this is a code block +``` \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code-block/docs-code-block.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-code-block/docs-code-block.spec.ts new file mode 100644 index 000000000000..d680c1cb701e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code-block/docs-code-block.spec.ts @@ -0,0 +1,24 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; +import {initHighlighter} from '../../extensions/docs-code/format/highlight'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + await initHighlighter(); + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-code-block/docs-code-block.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('converts triple ticks into a code block', () => { + const codeBlock = markdownDocument.querySelector('code'); + expect(codeBlock).toBeTruthy(); + expect(codeBlock?.textContent?.trim()).toBe('this is a code block'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code-multifile/docs-code-multifile.md b/adev/shared-docs/pipeline/guides/testing/docs-code-multifile/docs-code-multifile.md new file mode 100644 index 000000000000..af1c4329b15f --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code-multifile/docs-code-multifile.md @@ -0,0 +1,4 @@ + +this is code +this is also code + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code-multifile/docs-code-multifile.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-code-multifile/docs-code-multifile.spec.ts new file mode 100644 index 000000000000..5e451194254e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code-multifile/docs-code-multifile.spec.ts @@ -0,0 +1,31 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; +import {initHighlighter} from '../../extensions/docs-code/format/highlight'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + await initHighlighter(); + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-code-multifile/docs-code-multifile.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('converts triple ticks into a code block', () => { + const multiFileEl = markdownDocument.querySelector('.docs-code-multifile'); + expect(multiFileEl).toBeTruthy(); + + const codeBlockOne = multiFileEl!.children[0]!; + expect(codeBlockOne).toBeTruthy(); + expect(codeBlockOne?.textContent?.trim()).toBe('this is code'); + + const codeBlockTwo = multiFileEl!.children[1]!; + expect(codeBlockTwo).toBeTruthy(); + expect(codeBlockTwo?.textContent?.trim()).toBe('this is also code'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code/docs-code.md b/adev/shared-docs/pipeline/guides/testing/docs-code/docs-code.md new file mode 100644 index 000000000000..fdf3ed5bb90d --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code/docs-code.md @@ -0,0 +1,10 @@ + +this is code + + + + + + + diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code/docs-code.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-code/docs-code.spec.ts new file mode 100644 index 000000000000..0c874ab268a8 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code/docs-code.spec.ts @@ -0,0 +1,55 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; +import {initHighlighter} from '../../extensions/docs-code/format/highlight'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + await initHighlighter(); + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-code/docs-code.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('converts docs-code elements into a code block', () => { + const codeBlock = markdownDocument.querySelectorAll('code')[0]; + expect(codeBlock).toBeTruthy(); + expect(codeBlock?.textContent?.trim()).toBe('this is code'); + }); + + it('removes eslint comments from the code', () => { + const codeBlock = markdownDocument.querySelectorAll('code')[1]; + expect(codeBlock).toBeTruthy(); + expect(codeBlock?.textContent?.trim()).not.toContain('// eslint'); + }); + + it('extract regions from the code', () => { + // This unit test is sensible to additional node, like text nodes between the lines. + // The specific index here makes sure there is no space/linebreak between the code lines + const codeBlock = markdownDocument.querySelectorAll('code')[2]; + expect(codeBlock).toBeTruthy(); + + expect(codeBlock?.textContent?.trim()).toContain(`const x = 'within the region';`); + expect(codeBlock?.textContent?.trim()).not.toContain('docregion'); + }); + + it('properly shows the diff of two provided file paths', () => { + const codeBlock = markdownDocument.querySelectorAll('code')[3]; + expect(codeBlock).toBeTruthy(); + + const codeLines = codeBlock.querySelectorAll('.line'); + expect(codeLines[0].textContent).toContain('oldFuncName'); + expect(codeLines[0].classList.contains('remove')).toBeTrue(); + + expect(codeLines[1].textContent).toContain('newName'); + expect(codeLines[1].classList.contains('add')).toBeTrue(); + + expect(codeLines[2].classList.contains('add')).toBeFalse(); + expect(codeLines[2].classList.contains('remove')).toBeFalse(); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code/example-with-eslint-comment.ts b/adev/shared-docs/pipeline/guides/testing/docs-code/example-with-eslint-comment.ts new file mode 100644 index 000000000000..b1b743d87a29 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code/example-with-eslint-comment.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line +const x = 1; diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code/example-with-region.ts b/adev/shared-docs/pipeline/guides/testing/docs-code/example-with-region.ts new file mode 100644 index 000000000000..54423ecdc001 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code/example-with-region.ts @@ -0,0 +1,3 @@ +// #docregion something +const x = 'within the region'; +// #enddocregion something diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code/new-code.ts b/adev/shared-docs/pipeline/guides/testing/docs-code/new-code.ts new file mode 100644 index 000000000000..489da8bfa5a2 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code/new-code.ts @@ -0,0 +1,6 @@ +function newName(param: boolean) { + if (param) { + return false; + } + return true; +} diff --git a/adev/shared-docs/pipeline/guides/testing/docs-code/old-code.ts b/adev/shared-docs/pipeline/guides/testing/docs-code/old-code.ts new file mode 100644 index 000000000000..8eff6f07668f --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-code/old-code.ts @@ -0,0 +1,6 @@ +function oldFuncName(param: boolean) { + if (param) { + return false; + } + return 'yay!'; +} diff --git a/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/decoration.svg b/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/decoration.svg new file mode 100644 index 000000000000..73f6e51ac27c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/decoration.svg @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/docs-decorative-header.md b/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/docs-decorative-header.md new file mode 100644 index 000000000000..b1483621ab6e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/docs-decorative-header.md @@ -0,0 +1,3 @@ + +This is header text + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/docs-decorative-header.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/docs-decorative-header.spec.ts new file mode 100644 index 000000000000..2039e8204a2d --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-decorative-header/docs-decorative-header.spec.ts @@ -0,0 +1,28 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-decorative-header/docs-decorative-header.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('sets the custom title in the header', () => { + expect(markdownDocument.querySelector('h1')?.textContent).toBe('Custom Title'); + }); + + it('includes provided svgs', () => { + expect(markdownDocument.querySelector('svg')).toBeTruthy(); + }); + + it('passes the header text to the content', () => { + expect(markdownDocument.querySelector('p')?.textContent?.trim()).toBe('This is header text'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-pill-row/docs-pill-row.md b/adev/shared-docs/pipeline/guides/testing/docs-pill-row/docs-pill-row.md new file mode 100644 index 000000000000..f0a36fa2b371 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-pill-row/docs-pill-row.md @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-pill-row/docs-pill-row.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-pill-row/docs-pill-row.spec.ts new file mode 100644 index 000000000000..7b1f2a4f6d91 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-pill-row/docs-pill-row.spec.ts @@ -0,0 +1,22 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-pill-row/docs-pill-row.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should create a nav container with all of the docs pills inside', () => { + const navEl = markdownDocument.querySelector('nav'); + expect(navEl?.children.length).toBe(2); + expect(navEl?.classList.contains('docs-pill-row')).toBeTrue(); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-pill/docs-pill.md b/adev/shared-docs/pipeline/guides/testing/docs-pill/docs-pill.md new file mode 100644 index 000000000000..14dfb4bb7bb5 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-pill/docs-pill.md @@ -0,0 +1,4 @@ +Text to force this as inline. + + + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-pill/docs-pill.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-pill/docs-pill.spec.ts new file mode 100644 index 000000000000..04170f616cd8 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-pill/docs-pill.spec.ts @@ -0,0 +1,33 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-pill/docs-pill.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should render links to anchors on the same page', () => { + const samePageEl = markdownDocument.querySelectorAll('a.docs-pill')[0]; + expect(samePageEl.textContent?.trim()).toBe('Same Page'); + }); + + it('should render external links with _blank target and iconography', () => { + const samePageEl = markdownDocument.querySelectorAll('a.docs-pill')[1]; + expect(samePageEl.getAttribute('target')).toBe('_blank'); + expect(samePageEl.textContent?.trim()).toContain('External Page'); + expect(samePageEl.querySelector('docs-icon')?.textContent).toBe('open_in_new'); + }); + + it('should render internal links that are relative paths', () => { + const samePageEl = markdownDocument.querySelectorAll('a.docs-pill')[2]; + expect(samePageEl.textContent?.trim()).toBe('Another Page'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-step/docs-step.md b/adev/shared-docs/pipeline/guides/testing/docs-step/docs-step.md new file mode 100644 index 000000000000..7c46f019a653 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-step/docs-step.md @@ -0,0 +1,6 @@ + + Do the first thing. + + + Do another thing after that. + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-step/docs-step.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-step/docs-step.spec.ts new file mode 100644 index 000000000000..c477c7653e5b --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-step/docs-step.spec.ts @@ -0,0 +1,53 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-step/docs-step.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should create a list item for each step', () => { + const stepEls = markdownDocument.querySelectorAll('li')!; + expect(stepEls.length).toBe(2); + }); + + it('should render each step with the provided information', () => { + const [firstStepEl, secondStepEl] = markdownDocument.querySelectorAll('li'); + + const firstStepAEl = firstStepEl.querySelector('a')!; + const firstStepTextContentEl = firstStepEl.querySelector('p')!; + const firstStepHeadingEl = firstStepEl.querySelector('h3')!; + + expect(firstStepHeadingEl.textContent?.trim()).toBe('Step 1'); + expect(firstStepTextContentEl.textContent).toContain('first thing'); + expect(firstStepAEl.getAttribute('href')).toBe(`#${firstStepHeadingEl.getAttribute('id')}`); + expect(firstStepAEl.getAttribute('tabindex')).toBe('-1'); + + expect(secondStepEl.querySelector('h3')?.textContent?.trim()).toBe('Step B'); + expect(secondStepEl.querySelector('p')?.textContent).toContain('another thing'); + }); + + it('should create a self referencial anchor for the step', () => { + const firstStepEl = markdownDocument.querySelector('li')!; + const firstStepAEl = firstStepEl.querySelector('a')!; + const firstStepHeadingEl = firstStepEl.querySelector('h3')!; + + expect(firstStepAEl.getAttribute('href')).toBe(`#${firstStepHeadingEl.getAttribute('id')}`); + expect(firstStepAEl.getAttribute('tabindex')).toBe('-1'); + }); + + it('should create a a link that is not reachable via tab', () => { + const firstStepEl = markdownDocument.querySelector('li')!; + const firstStepAEl = firstStepEl.querySelector('a')!; + + expect(firstStepAEl.getAttribute('tabindex')).toBe('-1'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-video/docs-video.md b/adev/shared-docs/pipeline/guides/testing/docs-video/docs-video.md new file mode 100644 index 000000000000..a1f4f87d8e62 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-video/docs-video.md @@ -0,0 +1,2 @@ +This is a video: + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-video/docs-video.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-video/docs-video.spec.ts new file mode 100644 index 000000000000..22d6eae6c160 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-video/docs-video.spec.ts @@ -0,0 +1,28 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-video/docs-video.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should create an iframe in a container', () => { + const videoContainerEl = markdownDocument.querySelector('.docs-video-container')!; + const iframeEl = videoContainerEl.children[0]; + + expect(videoContainerEl.children.length).toBe(1); + + expect(iframeEl.nodeName).toBe('IFRAME'); + expect(iframeEl.getAttribute('src')).toBeTruthy(); + expect(iframeEl.classList.contains('docs-video')).toBeTrue(); + expect(iframeEl.getAttribute('title')).toBeTruthy(); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/docs-workflow/docs-workflow.md b/adev/shared-docs/pipeline/guides/testing/docs-workflow/docs-workflow.md new file mode 100644 index 000000000000..e5e74a90ab70 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-workflow/docs-workflow.md @@ -0,0 +1,8 @@ + + + Do the first thing. + + + Do another thing after that. + + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/docs-workflow/docs-workflow.spec.ts b/adev/shared-docs/pipeline/guides/testing/docs-workflow/docs-workflow.spec.ts new file mode 100644 index 000000000000..94b5afa3162f --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/docs-workflow/docs-workflow.spec.ts @@ -0,0 +1,22 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile( + runfiles.resolvePackageRelative('docs-workflow/docs-workflow.md'), + {encoding: 'utf-8'}, + ); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('create an ordered list container around the docs-steps', () => { + const docsWorkflowEl = markdownDocument.querySelector('.docs-steps')!; + expect(docsWorkflowEl.tagName).toBe('OL'); + expect(docsWorkflowEl.children.length).toBe(2); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/heading/heading.md b/adev/shared-docs/pipeline/guides/testing/heading/heading.md new file mode 100644 index 000000000000..1307a212f158 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/heading/heading.md @@ -0,0 +1,12 @@ +# Top Header (h1) +## Headers (h2) +### Smaller headers (h3) +#### Even smaller (h4) +##### Even more smaller (h5) +###### The smallest! (h6) +##### Another "more smaller" (h5) +## Duplicate Anchor +## Duplicate Anchor +## `myClass.myMethod` is the best +## ステップ 2 - アプリケーションのレイアウトに新しいコンポーネントを追加 +## My heading {# my-custom-id } \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/heading/heading.spec.ts b/adev/shared-docs/pipeline/guides/testing/heading/heading.spec.ts new file mode 100644 index 000000000000..2c1500962a7e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/heading/heading.spec.ts @@ -0,0 +1,86 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile(runfiles.resolvePackageRelative('heading/heading.md'), { + encoding: 'utf-8', + }); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should treat # as document headers', () => { + const header = markdownDocument.querySelector('header'); + expect(header?.classList.contains('docs-header')).toBeTrue(); + }); + + it('should create a self referential link for non document headers', () => { + const h2 = markdownDocument.querySelector('h2'); + const h2Anchor = h2?.firstElementChild; + + const h2HeaderId = h2?.getAttribute('id'); + const h2AnchorHref = h2Anchor?.getAttribute('href'); + + expect(h2HeaderId).toContain('headers-h2'); + expect(h2AnchorHref).toBe(`#${h2HeaderId}`); + }); + + it('should make the docs anchors unreachable by tab', () => { + const docsAnchors = markdownDocument.querySelectorAll('.docs-anchor'); + for (const anchor of docsAnchors) { + expect(anchor.getAttribute('tabindex')).toBe('-1'); + } + }); + + it('increments when multiple duplicate header names are found', () => { + const headers = markdownDocument.querySelectorAll('a.docs-anchor'); + const knownRefs = new Set(); + for (const el of headers) { + const href = el.getAttribute('href'); + expect(knownRefs.has(href!)).toBeFalse(); + knownRefs.add(href!); + } + }); + + it('should remove code block markups', () => { + const h2List = markdownDocument.querySelectorAll('h2'); + const h2 = h2List[3]; + const h2Anchor = h2?.firstElementChild; + + const h2HeaderId = h2?.getAttribute('id'); + const h2AnchorHref = h2Anchor?.getAttribute('href'); + + expect(h2HeaderId).toContain('myclassmymethod-is-the-best'); + expect(h2AnchorHref).toBe(`#${h2HeaderId}`); + }); + + it('should be able to extract non-ascii ids', () => { + const h2List = markdownDocument.querySelectorAll('h2'); + const h2 = h2List[4]; + const h2Anchor = h2?.firstElementChild; + + const h2HeaderId = h2?.getAttribute('id'); + const h2AnchorHref = h2Anchor?.getAttribute('href'); + + expect(h2HeaderId).toContain( + 'ステップ-2---アプリケーションのレイアウトに新しいコンポーネントを追加', + ); + expect(h2AnchorHref).toBe(`#${h2HeaderId}`); + }); + + it('should be able to extract custom ids', () => { + const h2List = markdownDocument.querySelectorAll('h2'); + const h2 = h2List[5]; + const h2Anchor = h2?.firstElementChild; + + const h2HeaderId = h2?.getAttribute('id'); + const h2AnchorHref = h2Anchor?.getAttribute('href'); + + expect(h2HeaderId).toBe('my-custom-id'); + expect(h2AnchorHref).toBe(`#${h2HeaderId}`); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/image/image.md b/adev/shared-docs/pipeline/guides/testing/image/image.md new file mode 100644 index 000000000000..f8e9bcee3728 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/image/image.md @@ -0,0 +1,2 @@ +![New Logo!](https://angular.dev/favicon.ico 'Our new icon') +![New Logo!](./some-image.png 'Local Image') \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/image/image.spec.ts b/adev/shared-docs/pipeline/guides/testing/image/image.spec.ts new file mode 100644 index 000000000000..a8d82100ad2f --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/image/image.spec.ts @@ -0,0 +1,25 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile(runfiles.resolvePackageRelative('image/image.md'), { + encoding: 'utf-8', + }); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should wrap images in custom classes', () => { + const image = markdownDocument.querySelector('img'); + expect(image?.classList.contains('docs-image')).toBeTrue(); + }); + + it('should handle images hosted internal to the application', () => { + const image = markdownDocument.querySelector('img[title="Local Image"]'); + expect(image?.getAttribute('src')).toBe('unknown/some-image.png'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/link/link.md b/adev/shared-docs/pipeline/guides/testing/link/link.md new file mode 100644 index 000000000000..a25130fef56e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/link/link.md @@ -0,0 +1,3 @@ +[Angular Site](https://angular.dev) +[same page](#test) +[same site](../other/page) \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/link/link.spec.ts b/adev/shared-docs/pipeline/guides/testing/link/link.spec.ts new file mode 100644 index 000000000000..05d5bd5a285c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/link/link.spec.ts @@ -0,0 +1,27 @@ +import {readFile} from 'fs/promises'; +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; + +describe('markdown to html', () => { + let parsedMarkdown: string; + beforeAll(async () => { + const markdownContent = await readFile(runfiles.resolvePackageRelative('link/link.md'), { + encoding: 'utf-8', + }); + parsedMarkdown = await parseMarkdown(markdownContent, {}); + }); + + it('should render external links with _blank target', () => { + expect(parsedMarkdown).toContain( + 'Angular Site', + ); + }); + + it('should render links to anchors on the same page', () => { + expect(parsedMarkdown).toContain('same page'); + }); + + it('should render internal links that are relative paths', () => { + expect(parsedMarkdown).toContain('same site'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/list/list.md b/adev/shared-docs/pipeline/guides/testing/list/list.md new file mode 100644 index 000000000000..2e6b9ffc4064 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/list/list.md @@ -0,0 +1,10 @@ +# Ordered List +1. First Item +2. Another Item +3. Last in order + +# Unordered List +- Order +- here +- matter +- doesn't \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/list/list.spec.ts b/adev/shared-docs/pipeline/guides/testing/list/list.spec.ts new file mode 100644 index 000000000000..fb1ffb9a9d2e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/list/list.spec.ts @@ -0,0 +1,27 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile(runfiles.resolvePackageRelative('list/list.md'), { + encoding: 'utf-8', + }); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should wrap lists in custom classes', () => { + const orderedList = markdownDocument.querySelector('ol'); + expect(orderedList?.className).toBe('docs-ordered-list'); + expect(orderedList?.childElementCount).toBe(3); + expect(orderedList?.textContent).toContain('First Item'); + + const unorderedList = markdownDocument.querySelector('ul'); + expect(unorderedList?.className).toBe('docs-list'); + expect(unorderedList?.childElementCount).toBe(4); + expect(unorderedList?.textContent).toContain('matter'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/mermaid/BUILD.bazel b/adev/shared-docs/pipeline/guides/testing/mermaid/BUILD.bazel new file mode 100644 index 000000000000..295f34d8c734 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/mermaid/BUILD.bazel @@ -0,0 +1,51 @@ +load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") + +ts_library( + name = "unit_test_lib", + testonly = True, + srcs = glob([ + "*.spec.ts", + ]), + deps = [ + "//adev/shared-docs/pipeline/guides", + "@npm//@bazel/runfiles", + "@npm//@types/jsdom", + "@npm//jsdom", + "@npm//marked", + ], +) + +ts_library( + name = "bootstrap", + testonly = True, + srcs = [ + "bootstrap.init.ts", + ], +) + +jasmine_node_test( + name = "unit_tests", + bootstrap = [ + ":bootstrap", + ], + data = [ + "@npm//@angular/build-tooling/bazel/browsers/chromium:chromium-for-generation", + "@npm//jsdom", + "@npm//mermaid", + "@npm//playwright-core", + ] + glob([ + "**/*.md", + "**/*.svg", + "**/*.ts", + ]), + env = { + "CHROME_BIN": "$(CHROMIUM)", + }, + tags = [ + "no-remote-exec", + ], + toolchains = [ + "@npm//@angular/build-tooling/bazel/browsers/chromium:toolchain_alias", + ], + deps = [":unit_test_lib"], +) diff --git a/adev/shared-docs/pipeline/guides/testing/mermaid/bootstrap.init.ts b/adev/shared-docs/pipeline/guides/testing/mermaid/bootstrap.init.ts new file mode 100644 index 000000000000..cf34cd255508 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/mermaid/bootstrap.init.ts @@ -0,0 +1,2 @@ +// Set HANDLE_MERMAID to true to test mermaid usage. +(global as any).HANDLE_MERMAID = true; diff --git a/adev/shared-docs/pipeline/guides/testing/mermaid/mermaid.md b/adev/shared-docs/pipeline/guides/testing/mermaid/mermaid.md new file mode 100644 index 000000000000..56daf4428210 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/mermaid/mermaid.md @@ -0,0 +1,14 @@ +```mermaid + graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` + +```mermaid + pie title Pets adopted by volunteers + "Dogs" : 386 + "Cats" : 85 + "Rats" : 15 +``` \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/mermaid/mermaid.spec.ts b/adev/shared-docs/pipeline/guides/testing/mermaid/mermaid.spec.ts new file mode 100644 index 000000000000..9229018a53e7 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/mermaid/mermaid.spec.ts @@ -0,0 +1,28 @@ +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; +import {marked} from 'marked'; +import {docsCodeBlockExtension} from '../../extensions/docs-code/docs-code-block'; +import {walkTokens} from '../../walk-tokens'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile(runfiles.resolvePackageRelative('./mermaid.md'), { + encoding: 'utf-8', + }); + + marked.use({ + async: true, + extensions: [docsCodeBlockExtension], + walkTokens, + }); + markdownDocument = JSDOM.fragment(await marked.parse(markdownContent)); + }); + + it('should create an svg for each mermaid code block', () => { + const svgs = markdownDocument.querySelectorAll('svg'); + expect(svgs.length).toBe(2); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/table/table.md b/adev/shared-docs/pipeline/guides/testing/table/table.md new file mode 100644 index 000000000000..d4dce00341a9 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/table/table.md @@ -0,0 +1,5 @@ +| Sports | Season | +| ---------------- | ------ | +| Skiing | Winter | +| Baseball | Summer | +| Running | Year Round | \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/table/table.spec.ts b/adev/shared-docs/pipeline/guides/testing/table/table.spec.ts new file mode 100644 index 000000000000..b1448a0601d8 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/table/table.spec.ts @@ -0,0 +1,27 @@ +import {readFile} from 'fs/promises'; +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; + +describe('markdown to html', () => { + let parsedMarkdown: string; + beforeAll(async () => { + const markdownContent = await readFile(runfiles.resolvePackageRelative('table/table.md'), { + encoding: 'utf-8', + }); + parsedMarkdown = await parseMarkdown(markdownContent, {}); + }); + + it('should wrap the table in custom div', () => { + expect(parsedMarkdown).toContain('
'); + }); + + it('should place the initial row as table header cells', () => { + expect(parsedMarkdown).toContain('Sports'); + expect(parsedMarkdown).toContain('Season'); + }); + + it('should place the subsequent rows as regular table cells', () => { + expect(parsedMarkdown).toContain('Baseball'); + expect(parsedMarkdown).toContain('Year Round'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/testing/text/text.md b/adev/shared-docs/pipeline/guides/testing/text/text.md new file mode 100644 index 000000000000..1960b2d29f70 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/text/text.md @@ -0,0 +1,5 @@ +This is a string that contains an emoji, 😎. See there it was; + +# This header causes there to be two text areas + +This is a string without an emoji. \ No newline at end of file diff --git a/adev/shared-docs/pipeline/guides/testing/text/text.spec.ts b/adev/shared-docs/pipeline/guides/testing/text/text.spec.ts new file mode 100644 index 000000000000..744e97559aa5 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/testing/text/text.spec.ts @@ -0,0 +1,27 @@ +import {parseMarkdown} from '../../../guides/parse'; +import {runfiles} from '@bazel/runfiles'; +import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; + +describe('markdown to html', () => { + let markdownDocument: DocumentFragment; + + beforeAll(async () => { + const markdownContent = await readFile(runfiles.resolvePackageRelative('text/text.md'), { + encoding: 'utf-8', + }); + markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, {})); + }); + + it('should wrap emoji in custom classes', () => { + const emoji = markdownDocument.querySelector('span.docs-emoji'); + expect(emoji).toBeTruthy(); + expect(emoji?.textContent).toContain('😎'); + }); + + it('should not apply a custom class if no emoji is present', () => { + const [, noemoji] = markdownDocument.querySelectorAll('p'); + expect(noemoji).toBeTruthy(); + expect(noemoji?.textContent).not.toContain('😎'); + }); +}); diff --git a/adev/shared-docs/pipeline/guides/tranformations/heading.ts b/adev/shared-docs/pipeline/guides/tranformations/heading.ts new file mode 100644 index 000000000000..b17d6c336e18 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/tranformations/heading.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {RendererApi} from 'marked'; + +import {getHeaderId} from '../state'; +import {getPageTitle} from '../utils'; + +export const headingRender: RendererApi['heading'] = (text, level, raw) => { + if (level === 1) { + return ` +
+ + + ${getPageTitle(text)} +
+ `; + } + + // Nested anchor elements are invalid in HTML + // They might happen when we have a code block in a heading + // regex aren't perfect for that but this one should be "good enough" + const regex = /]*?\s+)?href.*?>(.*?)<\/a>/gi; + const anchorLessText = text.replace(regex, '$1'); + + // extract the extended markdown heading id + // ex: ## MyHeading {# myId} + const customIdRegex = /{#\s*([\w-]+)\s*}/g; + const customId = customIdRegex.exec(anchorLessText)?.[1]; + const link = customId ?? getHeaderId(anchorLessText); + const label = anchorLessText.replace(/`(.*?)`/g, '$1').replace(customIdRegex, ''); + + return ` + + ${label} + + `; +}; diff --git a/adev/shared-docs/pipeline/guides/tranformations/image.ts b/adev/shared-docs/pipeline/guides/tranformations/image.ts new file mode 100644 index 000000000000..5346a024a2d1 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/tranformations/image.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {normalize} from 'path'; +import {RendererApi} from 'marked'; + +// TODO(josephperrott): Determine how we can define/know the image content base path. +const imageContentBasePath = 'unknown'; + +export const imageRender: RendererApi['image'] = (href, title, text) => { + const isRelativeSrc = href?.startsWith('./'); + const src = isRelativeSrc ? `${imageContentBasePath}/${normalize(href)}` : href; + return ` + ${text} + `; +}; diff --git a/adev/shared-docs/pipeline/guides/tranformations/link.ts b/adev/shared-docs/pipeline/guides/tranformations/link.ts new file mode 100644 index 000000000000..41b292ff26c1 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/tranformations/link.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {anchorTarget} from '../helpers'; +import {RendererApi} from 'marked'; + +export const linkRender: RendererApi['link'] = (href, title, text) => { + const titleAttribute = title ? ` title=${title}` : ''; + return `${text}`; +}; diff --git a/adev/shared-docs/pipeline/guides/tranformations/list.ts b/adev/shared-docs/pipeline/guides/tranformations/list.ts new file mode 100644 index 000000000000..a41e8f9d7cf9 --- /dev/null +++ b/adev/shared-docs/pipeline/guides/tranformations/list.ts @@ -0,0 +1,24 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {RendererApi} from 'marked'; + +export const listRender: RendererApi['list'] = (body, ordered, start) => { + if (ordered) { + return ` +
    + ${body} +
+ `; + } + return ` +
    + ${body} +
+ `; +}; diff --git a/adev/shared-docs/pipeline/guides/tranformations/table.ts b/adev/shared-docs/pipeline/guides/tranformations/table.ts new file mode 100644 index 000000000000..b4a1ab945b6f --- /dev/null +++ b/adev/shared-docs/pipeline/guides/tranformations/table.ts @@ -0,0 +1,24 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {RendererApi} from 'marked'; + +export const tableRender: RendererApi['table'] = (header, body) => { + return ` +
+ + + ${header} + + + ${body} + +
+
+ `; +}; diff --git a/adev/shared-docs/pipeline/guides/tranformations/text.ts b/adev/shared-docs/pipeline/guides/tranformations/text.ts new file mode 100644 index 000000000000..7366c9e27c1d --- /dev/null +++ b/adev/shared-docs/pipeline/guides/tranformations/text.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {RendererApi} from 'marked'; +import emojiRegex from 'emoji-regex'; + +/** Regex to find unicode emojis. */ +const UNICODE_EMOJI_REGEX = /&#x[\dA-Fa-f]+;/g; + +/** Regex to find emojis. */ +const regex = emojiRegex(); + +export const textRender: RendererApi['text'] = (text) => { + return regex.test(text) || UNICODE_EMOJI_REGEX.test(text) + ? `${text}` + : text; +}; diff --git a/adev/shared-docs/pipeline/guides/utils.ts b/adev/shared-docs/pipeline/guides/utils.ts new file mode 100644 index 000000000000..f7e74033931c --- /dev/null +++ b/adev/shared-docs/pipeline/guides/utils.ts @@ -0,0 +1,50 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {existsSync, readFileSync} from 'fs'; +import {join} from 'path'; +import {cwd} from 'process'; + +// TODO(josephperrott): Set edit content url based on the owner, repo and branch. + +/** The base url for edting the a file in the repository. */ +const GITHUB_EDIT_CONTENT_URL = 'https://github.com/angular/angular/edit/main'; + +/** Get the page title with edit button to modify the page source. */ +export function getPageTitle(text: string): string { + return ` + +
+

${text}

+ + + edit + +
`; +} + +/** Configuration using environment for parser, providing context. */ +export interface ParserContext { + markdownFilePath?: string; +} + +let context: ParserContext = {}; +export function setContext(envContext: Partial) { + context = envContext; +} + +/** The base directory of the workspace the script is running in. */ +const WORKSPACE_DIR = cwd(); + +export function loadWorkspaceRelativeFile(filePath: string): string { + const fullFilePath = join(WORKSPACE_DIR, filePath); + if (!existsSync(fullFilePath)) { + throw Error(`Cannot find: ${filePath}`); + } + return readFileSync(fullFilePath, {encoding: 'utf-8'}); +} diff --git a/adev/shared-docs/pipeline/guides/walk-tokens.ts b/adev/shared-docs/pipeline/guides/walk-tokens.ts new file mode 100644 index 000000000000..12e8ff878f5e --- /dev/null +++ b/adev/shared-docs/pipeline/guides/walk-tokens.ts @@ -0,0 +1,28 @@ +import {Token} from 'marked'; +import {DocsCodeToken} from './extensions/docs-code/docs-code'; + +/** + * Describe a HANDLE_MERMAID value which esbuild will use at build time to determine if the mermaid + * related code should be included in the bundle. + * THIS VALUE IS NOT AVAILABLE AT RUNTIME. + */ +export declare const HANDLE_MERMAID: boolean; + +/** Type guard for if a provided token is the DocsCodeToken. */ +function isDocsCodeToken(token: Token): token is DocsCodeToken { + return !!(token as DocsCodeToken).language; +} + +/** + * Handle the provided token based on the token itself replacing its content/data in place + * as appropriate. + */ +export async function walkTokens(token: Token): Promise { + if (!isDocsCodeToken(token) || token.language !== 'mermaid') { + return; + } + + if (HANDLE_MERMAID) { + return (await import('./mermaid')).processMermaidCodeBlock(token); + } +} diff --git a/adev/shared-docs/pipeline/tutorials/BUILD.bazel b/adev/shared-docs/pipeline/tutorials/BUILD.bazel new file mode 100644 index 000000000000..40e1384f93c0 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/BUILD.bazel @@ -0,0 +1,53 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "editor", + srcs = glob( + [ + "*.ts", + ], + exclude = [ + "playground.ts", + "tutorial.ts", + ], + ), + deps = [ + "//adev/shared-docs/interfaces", + "@npm//@types/node", + "@npm//@webcontainer/api", + "@npm//fast-glob", + ], +) + +ts_library( + name = "playground", + srcs = [ + "playground.ts", + ], + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + ":editor", + "//adev/shared-docs/interfaces", + "@npm//@types/node", + "@npm//fast-glob", + ], +) + +ts_library( + name = "tutorials", + srcs = [ + "tutorial.ts", + ], + visibility = [ + "//adev/shared-docs:__subpackages__", + ], + deps = [ + ":editor", + "//adev/shared-docs/interfaces", + "@npm//@types/node", + ], +) diff --git a/adev/shared-docs/pipeline/tutorials/common/.gitignore b/adev/shared-docs/pipeline/tutorials/common/.gitignore new file mode 100644 index 000000000000..0711527ef9d5 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-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 +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/adev/shared-docs/pipeline/tutorials/common/BUILD.bazel b/adev/shared-docs/pipeline/tutorials/common/BUILD.bazel new file mode 100644 index 000000000000..706e35dbb1c7 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/BUILD.bazel @@ -0,0 +1,5 @@ +filegroup( + name = "files", + srcs = glob(["**/*"]), + visibility = ["//visibility:public"], +) diff --git a/adev/shared-docs/pipeline/tutorials/common/angular.json b/adev/shared-docs/pipeline/tutorials/common/angular.json new file mode 100644 index 000000000000..b9d216be0bd3 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "first-app": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "inlineTemplate": true, + "inlineStyle": true, + "style": "scss", + "skipTests": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/first-app", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.css"], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "first-app:build:production" + }, + "development": { + "buildTarget": "first-app:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "first-app:build" + } + } + } + } + } +} diff --git a/adev/shared-docs/pipeline/tutorials/common/package-lock.json b/adev/shared-docs/pipeline/tutorials/common/package-lock.json new file mode 100644 index 000000000000..a7ddd37c4f8b --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/package-lock.json @@ -0,0 +1,12426 @@ +{ + "name": "angular.dev", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "angular.dev", + "version": "0.0.0", + "dependencies": { + "@angular/common": "^17.0.0-rc.1", + "@angular/compiler": "^17.0.0-rc.1", + "@angular/core": "^17.0.0-rc.1", + "@angular/platform-browser": "^17.0.0-rc.1", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.0.0-rc.2", + "@angular/cli": "^17.0.0-rc.2", + "@angular/compiler-cli": "^17.0.0-rc.1", + "typescript": "~5.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1700.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.0-rc.2.tgz", + "integrity": "sha512-+1GpG59fHgHNdqnDxXDa801vunrxSHDQohtS2s9ltuWDrl29vptdHLXCAWpP7OD+MD7+gab5jQqIDNCXats8gw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "17.0.0-rc.2", + "rxjs": "7.8.1" + }, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "17.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.0.0-rc.2.tgz", + "integrity": "sha512-lRWtwbZtk1VQ0XhJ97M0ijzLld5aAlx/CyxK1CBveP05i3FjdhQ4sH0MFf/bqkDr7kDc9WhDikryKEIdpcKE5w==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.2.1", + "@angular-devkit/architect": "0.1700.0-rc.2", + "@angular-devkit/build-webpack": "0.1700.0-rc.2", + "@angular-devkit/core": "17.0.0-rc.2", + "@babel/core": "7.23.2", + "@babel/generator": "7.23.0", + "@babel/helper-annotate-as-pure": "7.22.5", + "@babel/helper-split-export-declaration": "7.22.6", + "@babel/plugin-transform-async-generator-functions": "7.23.2", + "@babel/plugin-transform-async-to-generator": "7.22.5", + "@babel/plugin-transform-runtime": "7.23.2", + "@babel/preset-env": "7.23.2", + "@babel/runtime": "7.23.2", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "17.0.0-rc.2", + "@vitejs/plugin-basic-ssl": "1.0.1", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.16", + "babel-loader": "9.1.3", + "babel-plugin-istanbul": "6.1.1", + "browser-sync": "2.29.3", + "browserslist": "^4.21.5", + "chokidar": "3.5.3", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.20", + "css-loader": "6.8.1", + "esbuild-wasm": "0.19.5", + "fast-glob": "3.3.1", + "http-proxy-middleware": "2.0.6", + "https-proxy-agent": "7.0.2", + "inquirer": "9.2.11", + "jsonc-parser": "3.2.0", + "karma-source-map-support": "1.4.0", + "less": "4.2.0", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.5", + "mini-css-extract-plugin": "2.7.6", + "mrmime": "1.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "2.3.1", + "piscina": "4.1.0", + "postcss": "8.4.31", + "postcss-loader": "7.3.3", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.69.5", + "sass-loader": "13.3.2", + "semver": "7.5.4", + "source-map-loader": "4.0.1", + "source-map-support": "0.5.21", + "terser": "5.22.0", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.6.2", + "vite": "4.5.0", + "webpack": "5.89.0", + "webpack-dev-middleware": "6.1.1", + "webpack-dev-server": "4.15.1", + "webpack-merge": "5.10.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.19.5" + }, + "peerDependencies": { + "@angular/compiler-cli": "^17.0.0 || ^17.0.0-next.0", + "@angular/localize": "^17.0.0 || ^17.0.0-next.0", + "@angular/platform-server": "^17.0.0 || ^17.0.0-next.0", + "@angular/service-worker": "^17.0.0 || ^17.0.0-next.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^17.0.0 || ^17.0.0-next.1", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.2 <5.3" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1700.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1700.0-rc.2.tgz", + "integrity": "sha512-yOI3cXXpkLnTlqMqXik1ATFWCgPZdR7Rv3eG67mD8QaMIShubFb82HQLKcb34SQJxf6HntYw7X6d5xnKbHwnvQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1700.0-rc.2", + "rxjs": "7.8.1" + }, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "17.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.0-rc.2.tgz", + "integrity": "sha512-z68Y+JhcDsTv9TVOX5tmuR7bRrTMLCP/3AxUeJ9kZW4UdCeZA77ya0k7F1XpkyCb2JTM/5+yYXTigxMh6Jhr8g==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "17.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.0.0-rc.2.tgz", + "integrity": "sha512-WW4mQdCGZ2YA6g2r10571hKxCMwkiVEoOF69GjBzLfIr4YBF6bcXcczqgGyeOtbTT3xy7FbTcdNZ7/l9K4XLDQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "17.0.0-rc.2", + "jsonc-parser": "3.2.0", + "magic-string": "0.30.5", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli": { + "version": "17.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.0.0-rc.2.tgz", + "integrity": "sha512-2Xl2e5PzD3MyNpf3Urbs1esHBqY5faPcqsRwFvHC2yo+X0M1n64D6uuKQ5+gbPnEbUc5KKi04QiFEsCbPIW/mw==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1700.0-rc.2", + "@angular-devkit/core": "17.0.0-rc.2", + "@angular-devkit/schematics": "17.0.0-rc.2", + "@schematics/angular": "17.0.0-rc.2", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.1.1", + "inquirer": "9.2.11", + "jsonc-parser": "3.2.0", + "npm-package-arg": "11.0.1", + "npm-pick-manifest": "9.0.0", + "open": "8.4.2", + "ora": "5.4.1", + "pacote": "17.0.4", + "resolve": "1.22.8", + "semver": "7.5.4", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "17.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.0.0-rc.1.tgz", + "integrity": "sha512-E51n8DdHY6WvP2OCBK9saIeGOjqCsekTxJ9Dx52kD/ZSI5JyQHUmxnCiGbp6rbxPBSli/0CS/6OabX7tVYq2Mg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=18.13.0" + }, + "peerDependencies": { + "@angular/core": "17.0.0-rc.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "17.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.0.0-rc.1.tgz", + "integrity": "sha512-h1HNvTNbroMo2YUtn6K/i0LKQUlAPQJb/Bq+nz1TyxnDI5nYGbRJ8WycQyGwRVrfvLkghkkCGzQqIHEUmZ3rSw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=18.13.0" + }, + "peerDependencies": { + "@angular/core": "17.0.0-rc.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "17.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.0.0-rc.1.tgz", + "integrity": "sha512-qGom6m5NXj8mB2laXm/xv5th2Vo5iwR0eSOW5VbvU7mCAhwx6WwlWd/jNaf58w1GygrU9p0ay8CHOv4yIK26Kw==", + "dev": true, + "dependencies": { + "@babel/core": "7.23.2", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": ">=18.13.0" + }, + "peerDependencies": { + "@angular/compiler": "17.0.0-rc.1", + "typescript": ">=5.2 <5.3" + } + }, + "node_modules/@angular/core": { + "version": "17.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.0.0-rc.1.tgz", + "integrity": "sha512-qz5Xjye2XkmBp+4vSxpPVM6yXs/36oXx/FUQa6PDP/DrW9wrCxSELf2g9PHT84xDgq6BhBqca0a4fq+QFURTqg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=18.13.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "17.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.0.0-rc.1.tgz", + "integrity": "sha512-y1YzN36AmvWMKNjJ0P8OVcnymCuc16xt/hHmEgDDu7ky5TfBO5OTm26DVySdGVuJdkz0M1sTbyyRuGDrhxRu7A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=18.13.0" + }, + "peerDependencies": { + "@angular/animations": "17.0.0-rc.1", + "@angular/common": "17.0.0-rc.1", + "@angular/core": "17.0.0-rc.1" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", + "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", + "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", + "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", + "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", + "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", + "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", + "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", + "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", + "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", + "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", + "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", + "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", + "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", + "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", + "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", + "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.2.tgz", + "integrity": "sha512-XOntj6icgzMS58jPVtQpiuF6ZFWxQiJavISGx5KGjRj+3gqZr8+N6Kx+N9BApWzgS+DOjIZfXXj0ZesenOWDyA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", + "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.2", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.23.2", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.23.0", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.11", + "@babel/plugin-transform-classes": "^7.22.15", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.23.0", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.11", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.11", + "@babel/plugin-transform-for-of": "^7.22.15", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.11", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.23.0", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", + "@babel/plugin-transform-modules-systemjs": "^7.23.0", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", + "@babel/plugin-transform-numeric-separator": "^7.22.11", + "@babel/plugin-transform-object-rest-spread": "^7.22.15", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.11", + "@babel/plugin-transform-optional-chaining": "^7.23.0", + "@babel/plugin-transform-parameters": "^7.22.15", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.10", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.10", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "@babel/types": "^7.23.0", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", + "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", + "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", + "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", + "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", + "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", + "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", + "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", + "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", + "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", + "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", + "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", + "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", + "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", + "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", + "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", + "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", + "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", + "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", + "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", + "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", + "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", + "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@ljharb/through": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.11.tgz", + "integrity": "sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@ngtools/webpack": { + "version": "17.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.0.0-rc.2.tgz", + "integrity": "sha512-V/FEtXh0tnoUQsdY9Q7QrkjSF9tvKFhm5mTC/qj2WMq+e53cT/HbeAcUvZMyKsNmr/1SWm6xv4BhIwPHPIq7yg==", + "dev": true, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^17.0.0 || ^17.0.0-next.0", + "typescript": ">=5.2 <5.3", + "webpack": "^5.54.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.0.tgz", + "integrity": "sha512-2yThA1Es98orMkpSLVqlDZAMPK3jHJhifP2gnNUdk1754uZ8yI5c+ulCoVG+WlntQA6MzhrURMXjSd9Z7dJ2/Q==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/agent/node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.3.tgz", + "integrity": "sha512-UZp9NwK+AynTrKvHn5k3KviW/hA5eENmFsu3iAPe7sWRt0lFUdsY/wXIYjpDFe7cdSNwOIzbObfwgt6eL5/2zw==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", + "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.0.tgz", + "integrity": "sha512-wBqcGsMELZna0jDblGd7UXgOby45TQaMWmbFwWX+SEotk4HV6zG2t6rT9siyLhPk4P6YYqgfL1UO8nMWDBVJXQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.1.tgz", + "integrity": "sha512-Od/JMrgkjZ8alyBE0IzeqZDiF1jgMez9Gkc/OYrCkHHiXNwM0wc6s7+h+xM7kYDZkS0tAoOLr9VvygyE5+2F7g==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@schematics/angular": { + "version": "17.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.0.0-rc.2.tgz", + "integrity": "sha512-ZkQiiz2r8onJJEZlxJl9j2X12Tm7ZJaZlnxmbSryT8vkHX8g6xtB9RgLfSAoojrHj0n/io314fFnu9C0tBWsRQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "17.0.0-rc.2", + "@angular-devkit/schematics": "17.0.0-rc.2", + "jsonc-parser": "3.2.0" + }, + "engines": { + "node": ">=18.13.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.1.0.tgz", + "integrity": "sha512-89uOo6yh/oxaU8AeOUnVrTdVMcGk9Q1hJa7Hkvalc6G3Z3CupWk4Xe9djSgJm9fMkH69s0P0cVHUoKSOemLdng==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", + "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.1.0.tgz", + "integrity": "sha512-4VRpfJxs+8eLqzLVrZngVNExVA/zAhVbi4UT4zmtLi4xRd7vz5qie834OgkrGsLlLB1B2nz/3wUxT1XAUBe8gw==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", + "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.2.0.tgz", + "integrity": "sha512-KKATZ5orWfqd9ZG6MN8PtCIx4eevWSuGRKQvofnWXRpyMyUEpmrzg5M5BrCpjM+NfZ0RbNGOh5tCz/P2uoRqOA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1", + "tuf-js": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", + "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", + "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.12.tgz", + "integrity": "sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", + "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.2.tgz", + "integrity": "sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.15", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.15.tgz", + "integrity": "sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.44.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.6.tgz", + "integrity": "sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.6.tgz", + "integrity": "sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.13", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz", + "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.8.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", + "integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.25.1" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.8.tgz", + "integrity": "sha512-vGXshY9vim9CJjrpcS5raqSjEfKlJcWy2HNdgUasR66fAnVEYarrf1ULV4nfvpC1nZq/moA9qyqBcu83x+Jlrg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.3.tgz", + "integrity": "sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.35", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.35.tgz", + "integrity": "sha512-tIF57KB+ZvOBpAQwSaACfEu7htponHXaFzP7RfKYgsOS0NoYnn+9+jzp7bbq4fWerizI3dTB4NfAZoyeQKWJLw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", + "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", + "integrity": "sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dev": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each-series": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.16", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", + "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.32.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.29.3.tgz", + "integrity": "sha512-NiM38O6XU84+MN+gzspVmXV2fTOoe+jBqIBx3IBdhZrdeURr6ZgznJr/p+hQ+KzkKEiGH/GcC4SQFSL0jV49bg==", + "dev": true, + "dependencies": { + "browser-sync-client": "^2.29.3", + "browser-sync-ui": "^2.29.3", + "bs-recipes": "1.3.4", + "chalk": "4.1.2", + "chokidar": "^3.5.1", + "connect": "3.6.6", + "connect-history-api-fallback": "^1", + "dev-ip": "^1.0.1", + "easy-extender": "^2.3.4", + "eazy-logger": "^4.0.1", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "fs-extra": "3.0.1", + "http-proxy": "^1.18.1", + "immutable": "^3", + "localtunnel": "^2.0.1", + "micromatch": "^4.0.2", + "opn": "5.3.0", + "portscanner": "2.2.0", + "raw-body": "^2.3.2", + "resp-modifier": "6.0.2", + "rx": "4.1.0", + "send": "0.16.2", + "serve-index": "1.9.1", + "serve-static": "1.13.2", + "server-destroy": "1.0.1", + "socket.io": "^4.4.1", + "ua-parser-js": "^1.0.33", + "yargs": "^17.3.1" + }, + "bin": { + "browser-sync": "dist/bin.js" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/browser-sync-client": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.29.3.tgz", + "integrity": "sha512-4tK5JKCl7v/3aLbmCBMzpufiYLsB1+UI+7tUXCCp5qF0AllHy/jAqYu6k7hUF3hYtlClKpxExWaR+rH+ny07wQ==", + "dev": true, + "dependencies": { + "etag": "1.8.1", + "fresh": "0.5.2", + "mitt": "^1.1.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/browser-sync-ui": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.29.3.tgz", + "integrity": "sha512-kBYOIQjU/D/3kYtUIJtj82e797Egk1FB2broqItkr3i4eF1qiHbFCG6srksu9gWhfmuM/TNG76jMfzAdxEPakg==", + "dev": true, + "dependencies": { + "async-each-series": "0.1.1", + "chalk": "4.1.2", + "connect-history-api-fallback": "^1", + "immutable": "^3", + "server-destroy": "1.0.1", + "socket.io-client": "^4.4.1", + "stream-throttle": "^0.1.3" + } + }, + "node_modules/browser-sync-ui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/browser-sync-ui/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/browser-sync-ui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/browser-sync-ui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/browser-sync-ui/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync-ui/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/browser-sync/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/browser-sync/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/browser-sync/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/browser-sync/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-recipes": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.3.4.tgz", + "integrity": "sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw==", + "dev": true + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.0.tgz", + "integrity": "sha512-I7mVOPl3PUCeRub1U8YoGz2Lqv9WOBpobZ8RyWFXmReuILz+3OAyTa5oH3QPdtKZD7N0Yk00aLfzn0qvp8dZ1w==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001551", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz", + "integrity": "sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", + "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", + "dev": true, + "dependencies": { + "browserslist": "^4.22.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/critters": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.20.tgz", + "integrity": "sha512-CImNRorKOl5d8TWcnAz5n5izQ6HFsvz29k327/ELy6UFcmbiZNOsinaKvzv16WZR0P6etfSWYzE47C4/56B3Uw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^5.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.2", + "htmlparser2": "^8.0.2", + "postcss": "^8.4.23", + "pretty-bytes": "^5.3.0" + } + }, + "node_modules/critters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/critters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/critters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/critters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/critters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/critters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.21", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.3", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "dev": true + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/dev-ip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", + "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", + "dev": true, + "bin": { + "dev-ip": "lib/dev-ip.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/easy-extender": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", + "integrity": "sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==", + "dev": true, + "dependencies": { + "lodash": "^4.17.10" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/eazy-logger": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.0.1.tgz", + "integrity": "sha512-2GSFtnnC6U4IEKhEI7+PvdxrmjJ04mdsj3wHZTFiw0tUtG4HCWzTr13ZYTk8XOGnA1xQMaDljoBOYlk3D/MMSw==", + "dev": true, + "dependencies": { + "chalk": "4.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eazy-logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eazy-logger/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eazy-logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eazy-logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eazy-logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eazy-logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.559", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz", + "integrity": "sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.3.tgz", + "integrity": "sha512-IML/R4eG/pUS5w7OfcDE0jKrljWS9nwnEfsxWCIJF5eO6AHo6+Hlv+lQbdlAYsiJPHzUthLm1RUjnBzWOs45cw==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", + "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", + "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.5", + "@esbuild/android-arm64": "0.19.5", + "@esbuild/android-x64": "0.19.5", + "@esbuild/darwin-arm64": "0.19.5", + "@esbuild/darwin-x64": "0.19.5", + "@esbuild/freebsd-arm64": "0.19.5", + "@esbuild/freebsd-x64": "0.19.5", + "@esbuild/linux-arm": "0.19.5", + "@esbuild/linux-arm64": "0.19.5", + "@esbuild/linux-ia32": "0.19.5", + "@esbuild/linux-loong64": "0.19.5", + "@esbuild/linux-mips64el": "0.19.5", + "@esbuild/linux-ppc64": "0.19.5", + "@esbuild/linux-riscv64": "0.19.5", + "@esbuild/linux-s390x": "0.19.5", + "@esbuild/linux-x64": "0.19.5", + "@esbuild/netbsd-x64": "0.19.5", + "@esbuild/openbsd-x64": "0.19.5", + "@esbuild/sunos-x64": "0.19.5", + "@esbuild/win32-arm64": "0.19.5", + "@esbuild/win32-ia32": "0.19.5", + "@esbuild/win32-x64": "0.19.5" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.19.5.tgz", + "integrity": "sha512-7zmLLn2QCj93XfMmHtzrDJ1UBuOHB2CZz1ghoCEZiRajxjUvHsF40PnbzFIY/pmesqPRaEtEWii0uzsTbnAgrA==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/express/node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "dependencies": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "node_modules/hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", + "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer": { + "version": "9.2.11", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.11.tgz", + "integrity": "sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==", + "dev": true, + "dependencies": { + "@ljharb/through": "^2.3.9", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "dev": true, + "dependencies": { + "lodash.isfinite": "^3.3.2" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", + "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", + "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "dev": true, + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/localtunnel": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-2.0.2.tgz", + "integrity": "sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==", + "dev": true, + "dependencies": { + "axios": "0.21.4", + "debug": "4.3.2", + "openurl": "1.1.1", + "yargs": "17.1.1" + }, + "bin": { + "lt": "bin/lt.js" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/localtunnel/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/localtunnel/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/localtunnel/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/localtunnel/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/localtunnel/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/localtunnel/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/localtunnel/node_modules/yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/localtunnel/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "17.1.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", + "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^7.0.3", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", + "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/mitt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", + "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.0.tgz", + "integrity": "sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^11.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp-build": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", + "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", + "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.0.tgz", + "integrity": "sha512-ErAGFB5kJUciPy1mmx/C2YFbvxoJ0QJ9uwkCZOeR6CqLLISPZBOiFModAbSXnjjlwW5lOhuhXva+fURsSGJqyw==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", + "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.1.0.tgz", + "integrity": "sha512-PQCELXKt8Azvxnt5Y85GseQDJJlglTFM9L9U9gkv2y4e9s0k3GVDdOx3YoB6gm2Do0hlkzC39iCGXby+Wve1Bw==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", + "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openurl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", + "integrity": "sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==", + "dev": true + }, + "node_modules/opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/opn/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.4.tgz", + "integrity": "sha512-eGdLHrV/g5b5MtD5cTPyss+JxOlaOloSMG3UwPMAvL8ywaLJ6beONPF40K4KKl/UI6q5hTKCJq5rCu8tkF+7Dg==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/piscina": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.1.0.tgz", + "integrity": "sha512-sjbLMi3sokkie+qmtZpkfMCUJTpbxJm/wvaPzU28vmYSsTSW8xk9JcFUsbqGJdtPpIQ9tuj+iDcTtgZjwnOSig==", + "dev": true, + "dependencies": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0" + }, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "dependencies": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + }, + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", + "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.2.0", + "jiti": "^1.18.2", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-package-json": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", + "integrity": "sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resp-modifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz", + "integrity": "sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw==", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/resp-modifier/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/resp-modifier/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/resp-modifier/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/resp-modifier/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "dev": true + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sass": { + "version": "1.69.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", + "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "dev": true + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "dev": true, + "optional": true + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true, + "bin": { + "mime": "cli.js" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/send/node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.1.0.tgz", + "integrity": "sha512-kPIj+ZLkyI3QaM0qX8V/nSsweYND3W448pwkDgS6CQ74MfhEkIR8ToK5Iyx46KJYRjseVcD3Rp9zAmUAj6ZjPw==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/sign": "^2.1.0", + "@sigstore/tuf": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz", + "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-throttle": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", + "integrity": "sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==", + "dev": true, + "dependencies": { + "commander": "^2.2.0", + "limiter": "^1.0.5" + }, + "bin": { + "throttleproxy": "bin/throttleproxy.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/terser": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", + "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tuf-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.1.0.tgz", + "integrity": "sha512-eD7YPPjVlMzdggrOeE8zwoegUaG/rt6Bt3jwoQPunRiNVzgcCE009UDFJKJjG+Gk9wFu6W/Vi+P5d/5QpdD9jA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/make-fetch-happen": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", + "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.36.tgz", + "integrity": "sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", + "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz", + "integrity": "sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.12", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.0.tgz", + "integrity": "sha512-Sz0G0TjMuyApIcuTJeK742+xLLKEPjYtkdBEazBlYePHkICVp9DPKqI/4dJt3LCtQBd52sCxz23uAFJ2OJa6Ow==", + "dependencies": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/adev/shared-docs/pipeline/tutorials/common/package.json.template b/adev/shared-docs/pipeline/tutorials/common/package.json.template new file mode 100644 index 000000000000..ec3ac40c8a6c --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/package.json.template @@ -0,0 +1,26 @@ +{ + "name": "angular.dev", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "NG_BUILD_PARALLEL_TS=0 ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development" + }, + "private": true, + "dependencies": { + "@angular/common": "^17.0.0-rc.1", + "@angular/compiler": "^17.0.0-rc.1", + "@angular/core": "^17.0.0-rc.1", + "@angular/platform-browser": "^17.0.0-rc.1", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^17.0.0-rc.2", + "@angular/cli": "^17.0.0-rc.2", + "@angular/compiler-cli": "^17.0.0-rc.1", + "typescript": "~5.2.0" + } +} diff --git a/adev/shared-docs/pipeline/tutorials/common/src/app/app.config.ts b/adev/shared-docs/pipeline/tutorials/common/src/app/app.config.ts new file mode 100644 index 000000000000..56bb7c8e971e --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/src/app/app.config.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ApplicationConfig} from '@angular/core'; + +export const appConfig: ApplicationConfig = { + providers: [], +}; diff --git a/adev/shared-docs/pipeline/tutorials/common/src/assets/.gitkeep b/adev/shared-docs/pipeline/tutorials/common/src/assets/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/adev/shared-docs/pipeline/tutorials/common/src/assets/angular.svg b/adev/shared-docs/pipeline/tutorials/common/src/assets/angular.svg new file mode 100644 index 000000000000..57d353b2643c --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/src/assets/angular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/adev/shared-docs/pipeline/tutorials/common/src/favicon.ico b/adev/shared-docs/pipeline/tutorials/common/src/favicon.ico new file mode 100644 index 000000000000..997406ad22c2 Binary files /dev/null and b/adev/shared-docs/pipeline/tutorials/common/src/favicon.ico differ diff --git a/adev/shared-docs/pipeline/tutorials/common/src/index.html b/adev/shared-docs/pipeline/tutorials/common/src/index.html new file mode 100644 index 000000000000..95e7cf90822c --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/src/index.html @@ -0,0 +1,13 @@ + + + + + Common + + + + + + + + diff --git a/adev/shared-docs/pipeline/tutorials/common/src/main.ts b/adev/shared-docs/pipeline/tutorials/common/src/main.ts new file mode 100644 index 000000000000..0ff1c273f4cf --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/src/main.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {bootstrapApplication} from '@angular/platform-browser'; +import {appConfig} from './app/app.config'; +import {AppComponent} from './app/app.component'; + +bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err)); diff --git a/adev/shared-docs/pipeline/tutorials/common/src/styles.css b/adev/shared-docs/pipeline/tutorials/common/src/styles.css new file mode 100644 index 000000000000..2a37a8dfc705 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/src/styles.css @@ -0,0 +1,4 @@ +/* You can add global styles to this file, and also import other style files */ +body { + font-family: 'Be Vietnam Pro', sans-serif; +} \ No newline at end of file diff --git a/adev/shared-docs/pipeline/tutorials/common/tsconfig.app.json b/adev/shared-docs/pipeline/tutorials/common/tsconfig.app.json new file mode 100644 index 000000000000..f64f655d52d1 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/adev/shared-docs/pipeline/tutorials/common/tsconfig.json b/adev/shared-docs/pipeline/tutorials/common/tsconfig.json new file mode 100644 index 000000000000..bc12e24b138b --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/common/tsconfig.json @@ -0,0 +1,31 @@ +/* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true, + "_enabledBlockTypes": ["if", "for"] + } +} diff --git a/adev/shared-docs/pipeline/tutorials/metadata.ts b/adev/shared-docs/pipeline/tutorials/metadata.ts new file mode 100644 index 000000000000..fd860845c516 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/metadata.ts @@ -0,0 +1,73 @@ +import {join, dirname} from 'path'; +import {glob} from 'fast-glob'; +import { + FileAndContentRecord, + PackageJson, + TutorialConfig, + TutorialMetadata, +} from '../../interfaces'; +import {getFileContents} from './utils'; + +/** Generate the metadata.json content for a provided tutorial config. */ +export async function generateMetadata( + path: string, + config: TutorialConfig, + files: FileAndContentRecord, +): Promise { + const tutorialFiles: FileAndContentRecord = {}; + const {dependencies, devDependencies} = JSON.parse(files['package.json']) as PackageJson; + + config.openFiles?.forEach((file) => (tutorialFiles[file] = files[file])); + + return { + type: config.type, + openFiles: config.openFiles || [], + allFiles: Object.keys(files), + tutorialFiles, + answerFiles: await getAnswerFiles(path, config, files), + hiddenFiles: config.openFiles + ? Object.keys(files).filter((filename) => !config.openFiles!.includes(filename)) + : [], + dependencies: { + ...dependencies, + ...devDependencies, + }, + }; +} + +/** Generate the answer files for the metadata.json. */ +async function getAnswerFiles( + path: string, + config: TutorialConfig, + files: FileAndContentRecord, +): Promise { + const answerFiles: FileAndContentRecord = {}; + const answerPrefix = 'answer/'; + + if (config.answerSrc) { + const answersDir = join(path, config.answerSrc); + const answerFilePaths = await glob('**/*', { + cwd: answersDir, + onlyFiles: true, + absolute: true, + }); + answerFilePaths.forEach((absolutePath) => { + // We use the absolute file in order to read the content, but the key + // needs to be a relative path within the project. + const parentDir = dirname(answersDir) + '/'; + const pathStart = absolutePath.indexOf(parentDir); + if (pathStart === -1) { + throw new Error('Invalid state: could not find start of answers path'); + } + answerFiles[absolutePath.slice(pathStart + parentDir.length)] = getFileContents(absolutePath); + }); + } else { + Object.keys(files).forEach((file) => { + if (file.includes(answerPrefix)) { + answerFiles[file.replace(answerPrefix, '')] = files[file]; + } + }); + } + + return answerFiles; +} diff --git a/adev/shared-docs/pipeline/tutorials/playground.ts b/adev/shared-docs/pipeline/tutorials/playground.ts new file mode 100644 index 000000000000..a6188ea29af4 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/playground.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {join} from 'path'; +import {FileAndContentRecord} from '../../interfaces'; +import {existsSync, mkdirSync, writeFileSync} from 'fs'; +import {addDirectoryToFilesRecord, findAllConfigs} from './utils'; +import {generateMetadata} from './metadata'; +import {generateSourceCode} from './source-code'; +import {generatePlaygroundRoutes} from './routes'; + +/** + * Generates the playground files for the playground directory. + * + * Creates a routes file for the overall playground, and metadata and soure-code files for + * each of the plaground entries. + */ +async function generatePlaygroundFiles( + playgroundDir: string, + commonDir: string, + outputDir: string, +) { + /** All files available in the playground entries. */ + const files: FileAndContentRecord = {}; + /** All of the configs, one for each playground entry. */ + const configs = await findAllConfigs(playgroundDir); + + // Add all of the files from the common directory into the files record. + await addDirectoryToFilesRecord(files, commonDir); + + // If the playground directory provides additional common files, add them to the files record. + const commonPlaygroundDir = join(playgroundDir, 'common'); + if (existsSync(commonPlaygroundDir)) { + await addDirectoryToFilesRecord(files, commonPlaygroundDir); + } + + // For each playground entry, generate the metadata and source-code files. + for (const [path, config] of Object.entries(configs)) { + /** Duplication of the common shared files to add the playground entry files in. */ + const itemFiles = {...files}; + /** Directory of the current config. */ + const configDir = join(playgroundDir, path); + + await addDirectoryToFilesRecord(itemFiles, configDir); + + // Ensure the directory for the playground entry exists, then write the metadata + // and source-code files. + mkdirSync(join(outputDir, path), {recursive: true}); + writeFileSync( + join(outputDir, path, 'metadata.json'), + JSON.stringify(await generateMetadata(configDir, config, itemFiles)), + ); + writeFileSync( + join(outputDir, path, 'source-code.json'), + JSON.stringify(await generateSourceCode(config, itemFiles)), + ); + } + + // Generate the playground routes, and write the file. + writeFileSync( + join(outputDir, 'routes.json'), + JSON.stringify(await generatePlaygroundRoutes(configs)), + ); +} + +// Immediately invoke the generation. +(async () => { + const [playgroundDir, commonDir, outputDir] = process.argv.slice(2); + await generatePlaygroundFiles(playgroundDir, commonDir, outputDir); +})(); diff --git a/adev/shared-docs/pipeline/tutorials/routes.ts b/adev/shared-docs/pipeline/tutorials/routes.ts new file mode 100644 index 000000000000..2506e081c858 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/routes.ts @@ -0,0 +1,73 @@ +import { + PlaygroundRouteData, + TutorialConfig, + TutorialNavigationItemWithStep, + TutorialNavigationItem, +} from '../../interfaces'; + +export async function generatePlaygroundRoutes( + configs: Record, +): Promise { + const templates = Object.entries(configs).map(([path, config]) => ({ + path: `playground/${path}`, + label: config.title, + })); + + return { + templates, + defaultTemplate: templates[0], + starterTemplate: templates[templates.length - 1], + }; +} + +export async function generateTutorialRoutes( + tutorialName: string, + introConfig: TutorialConfig, + stepConfigs: Record, +): Promise { + const children: TutorialNavigationItem[] = Object.entries(stepConfigs) + // Sort using the number prefix from the step directory name. + .sort(([pathA], [pathB]) => + Number(pathA.split('-')[0]) > Number(pathB.split('-')[0]) ? 1 : -1, + ) + .map(([path, config], idx) => { + return { + label: config.title, + path: `tutorials/${tutorialName}/${path}`, + contentPath: `tutorials/${tutorialName}/steps/${path}/README`, + tutorialData: { + title: config.title, + type: config.type, + step: idx + 1, + }, + }; + }); + + children.forEach((child, idx, childrenArr) => { + if (idx > 0) { + const prevStep = childrenArr.at(idx - 1); + if (prevStep) { + child.tutorialData.previousStep = prevStep.path; + } + } + if (idx < childrenArr.length - 1) { + const nextStep = childrenArr.at(idx + 1); + if (nextStep) { + child.tutorialData.nextStep = nextStep.path; + } + } + }); + + return { + path: `tutorials/${tutorialName}`, + label: introConfig.title, + contentPath: `tutorials/${tutorialName}/intro/README`, + tutorialData: { + step: 0, + title: introConfig.title, + type: introConfig.type, + nextStep: children[0].path, + }, + children: children, + }; +} diff --git a/adev/shared-docs/pipeline/tutorials/source-code.ts b/adev/shared-docs/pipeline/tutorials/source-code.ts new file mode 100644 index 000000000000..63b6ddfc5990 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/source-code.ts @@ -0,0 +1,13 @@ +import {FileSystemTree} from '@webcontainer/api'; +import {FileAndContentRecord, TutorialConfig} from '../../interfaces'; +import {getFileSystemTree} from './webcontainers'; + +/** Generate the source-code.json content for a provided tutorial config. */ +export async function generateSourceCode( + config: TutorialConfig, + files: FileAndContentRecord, +): Promise { + // TODO(josephperrott): figure out if filtering is needed for this. + const allFiles = Object.keys(files); + return getFileSystemTree(allFiles, files); +} diff --git a/adev/shared-docs/pipeline/tutorials/tutorial.ts b/adev/shared-docs/pipeline/tutorials/tutorial.ts new file mode 100644 index 000000000000..7d568d930136 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/tutorial.ts @@ -0,0 +1,92 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {basename, join} from 'path'; +import {FileAndContentRecord} from '../../interfaces'; +import {existsSync, mkdirSync, writeFileSync} from 'fs'; +import {addDirectoryToFilesRecord, findAllConfigs, findConfig} from './utils'; +import {generateMetadata} from './metadata'; +import {generateSourceCode} from './source-code'; +import {generateTutorialRoutes} from './routes'; + +/** + * Generates the files for the provided tutorial directory. + * + * Creates a routes file for the tutorial, and metadata and soure-code files for + * each of the tutorial steps. + */ +async function generateTutorialFiles(tutorialDir: string, commonDir: string, outputDir: string) { + /** All files available in the tutorial entries. */ + const files: FileAndContentRecord = {}; + /** List of configs for each step in the tutorial. */ + const stepConfigs = await findAllConfigs(join(tutorialDir, 'steps')); + /** Directory of the intro. */ + const introDir = join(tutorialDir, 'intro'); + /** The configuration for the intro (landing page) of the tutorial. */ + const introConfig = await findConfig(introDir); + /** The name of the tutorial, as determined by the tutorial directory name. */ + const tutorialName = basename(tutorialDir); + + // Add all of the files from the common directory into the files record + await addDirectoryToFilesRecord(files, commonDir); + + // If the tutorial directory provides additional common files, add them to the files record. + const commonTutorialDir = join(tutorialDir, 'common'); + if (existsSync(commonTutorialDir)) { + await addDirectoryToFilesRecord(files, commonTutorialDir); + } + + /** Duplication of the common shared files to add the tutorial intro files in. */ + const introFiles = {...files}; + await addDirectoryToFilesRecord(introFiles, introDir); + + // Ensure the directory for the tutorial exists, then write the metadata and source-code + // files for the intro. + mkdirSync(join(outputDir), {recursive: true}); + writeFileSync( + join(outputDir, 'metadata.json'), + JSON.stringify(await generateMetadata(introDir, introConfig, introFiles)), + ); + writeFileSync( + join(outputDir, 'source-code.json'), + JSON.stringify(await generateSourceCode(introConfig, introFiles)), + ); + + // For each tutorial step, generate the metadata and source-code files. + for (const [path, config] of Object.entries(stepConfigs)) { + /** Duplication of the common shared files to add the tutorial step files in. */ + const itemFiles = {...files}; + /** Directory of the current step. */ + const stepDir = join(tutorialDir, 'steps', path); + + await addDirectoryToFilesRecord(itemFiles, stepDir); + + // Ensure the directory for the tutorial step exists, then write the metadata + // and source-code files. + mkdirSync(join(outputDir, path), {recursive: true}); + writeFileSync( + join(outputDir, path, 'metadata.json'), + JSON.stringify(await generateMetadata(stepDir, config, itemFiles)), + ); + writeFileSync( + join(outputDir, path, 'source-code.json'), + JSON.stringify(await generateSourceCode(config, itemFiles)), + ); + } + + // Generate the tutorial routes, and write the file. + writeFileSync( + join(outputDir, 'routes.json'), + JSON.stringify(await generateTutorialRoutes(tutorialName, introConfig, stepConfigs)), + ); +} + +(async () => { + const [tutorialDir, commonDir, outputDir] = process.argv.slice(2); + await generateTutorialFiles(tutorialDir, commonDir, outputDir); +})(); diff --git a/adev/shared-docs/pipeline/tutorials/utils.ts b/adev/shared-docs/pipeline/tutorials/utils.ts new file mode 100644 index 000000000000..e92754599d94 --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/utils.ts @@ -0,0 +1,94 @@ +import {glob} from 'fast-glob'; +import {FileAndContentRecord, TutorialConfig} from '../../interfaces'; +import {dirname, join} from 'path'; +import {existsSync, readFileSync} from 'fs'; + +// See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files for details +// on identifying file types with initial bytes. +/** Initial bytes of the buffer(aka magic numbers) to see if it's a JPG file. */ +const jpgSignature = [0xff, 0xd8, 0xff]; +/** Initial bytes of the buffer(aka magic numbers) to see if it's a GIF file. */ +const gifSignature = [0x47, 0x49, 0x46]; +/** Initial bytes of the buffer(aka magic numbers) to see if it's a PNG file. */ +const pngSignature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; + +/** List of initial bytes to check for matching files. */ +const SIGNATURES = [jpgSignature, gifSignature, pngSignature]; + +/** + * Get the contents for the provided file, returning a string or Buffer as appropriate. + */ +export function getFileContents(path: string): T; +export function getFileContents(path: string): string | Uint8Array { + const fileBuffer = readFileSync(path); + + if (checkBufferMatchForSignatures(fileBuffer)) { + return fileBuffer; + } + + return fileBuffer.toString(); +} + +/** + * Determine if the initial bytes of a buffer matches the expected bytes. + */ +function checkBufferMatchForSignatures(buffer: Uint8Array): boolean { + for (const initialByes of SIGNATURES) { + for (const [index, byte] of initialByes.entries()) { + if (buffer[index] !== byte) return false; + } + } + + return true; +} + +/** + * Add all files found in the provided directory into the provided object of file and contents. + * This overwrite already existing files in the object when encountered. + */ +export async function addDirectoryToFilesRecord( + files: FileAndContentRecord, + dir: string, +): Promise { + const exampleFilePaths = await glob('**/*', { + cwd: dir, + onlyFiles: true, + }); + + for (let path of exampleFilePaths) { + files[path] = await getFileContents(join(dir, path)); + } +} + +/** + * Collect all of the config.json files in the provided directory and subdirectories. + */ +export async function findAllConfigs(dir: string): Promise> { + const configs: Record = {}; + + const paths = await glob('**/config.json', { + cwd: dir, + onlyFiles: true, + }); + + for (const path of paths) { + const content = await getFileContents(join(dir, path)); + configs[dirname(path)] = JSON.parse(content) as TutorialConfig; + } + + return configs; +} + +/** + * Collect a single of the config.json file at the provided directory. + */ +export async function findConfig(dir: string): Promise { + const configPath = join(dir, 'config.json'); + + if (!existsSync(configPath)) { + throw Error(`Unable config.json file found at: ${dir}`); + } + + const content = await getFileContents(configPath); + return JSON.parse(content) as TutorialConfig; +} diff --git a/adev/shared-docs/pipeline/tutorials/webcontainers.ts b/adev/shared-docs/pipeline/tutorials/webcontainers.ts new file mode 100644 index 000000000000..6c15043c2e7c --- /dev/null +++ b/adev/shared-docs/pipeline/tutorials/webcontainers.ts @@ -0,0 +1,86 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {basename, dirname, extname} from 'path'; +import type {DirectoryNode, FileNode, FileSystemTree} from '@webcontainer/api'; +import {FileAndContent, FileAndContentRecord} from '../../interfaces'; + +/** + * Create a WebContainer's FileSystemTree from a list of files and its contents + */ +export function getFileSystemTree(files: string[], filesContents: FileAndContentRecord) { + const fileSystemTree: FileSystemTree = {}; + + for (let filepath of files) { + const dir = dirname(filepath); + const filename = basename(filepath); + + if (dir === '.') { + const fileNode: FileNode = {file: {contents: filesContents[filepath]}}; + + fileSystemTree[filename] = fileNode; + } else { + const dirParts = dir.split('/'); + + buildFileSystemTree(fileSystemTree, dirParts, filename, filesContents[filepath]); + } + } + + return fileSystemTree; +} + +/** + * Builds a WebContainer's file system tree object recursively, mutating the + * `fileSystemTree` parameter. + * + * @see https://webcontainers.io/api#filesystemtree + */ +function buildFileSystemTree( + fileSystemTree: FileSystemTree, + fileDirectories: string[], + filename: FileAndContent['path'], + fileContents: FileAndContent['content'], +): void { + if (fileDirectories.length === 1) { + const directory = fileDirectories[0]; + + const fileNode: FileNode = {file: {contents: fileContents}}; + + fileSystemTree[directory] = { + ...fileSystemTree[directory], + + directory: { + ...(fileSystemTree[directory] + ? (fileSystemTree[directory] as DirectoryNode).directory + : undefined), + + [filename]: fileNode, + }, + }; + + return; + } + + const nextDirectory = fileDirectories.shift(); + if (!nextDirectory) return; + + if (!fileSystemTree[nextDirectory]) { + fileSystemTree[nextDirectory] = {directory: {}}; + } + + buildFileSystemTree( + (fileSystemTree[nextDirectory] as DirectoryNode).directory, + fileDirectories, + filename, + fileContents, + ); +} + +export function shouldUseFileInWebContainer(filename: string) { + return ['.md', '.png', '.jpg'].includes(extname(filename)) === false; +} diff --git a/adev/shared-docs/pipes/BUILD.bazel b/adev/shared-docs/pipes/BUILD.bazel new file mode 100644 index 000000000000..0650f6c56317 --- /dev/null +++ b/adev/shared-docs/pipes/BUILD.bazel @@ -0,0 +1,32 @@ +load("//tools:defaults.bzl", "ng_module", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "pipes", + srcs = [ + "index.ts", + ], + visibility = ["//adev/shared-docs:__subpackages__"], + deps = [ + ":lib", + ], +) + +ng_module( + name = "lib", + srcs = glob( + [ + "**/*.ts", + ], + exclude = [ + "index.ts", + "**/*.spec.ts", + ], + ), + deps = [ + "//adev/shared-docs/interfaces", + "//adev/shared-docs/utils", + "//packages/core", + ], +) diff --git a/adev/shared-docs/pipes/index.ts b/adev/shared-docs/pipes/index.ts new file mode 100644 index 000000000000..f32b2d8c9a0d --- /dev/null +++ b/adev/shared-docs/pipes/index.ts @@ -0,0 +1,2 @@ +export * from './is-active-navigation-item.pipe'; +export * from './relative-link.pipe'; diff --git a/adev/shared-docs/pipes/is-active-navigation-item.pipe.spec.ts b/adev/shared-docs/pipes/is-active-navigation-item.pipe.spec.ts new file mode 100644 index 000000000000..9688899fe982 --- /dev/null +++ b/adev/shared-docs/pipes/is-active-navigation-item.pipe.spec.ts @@ -0,0 +1,66 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {NavigationItem} from '../interfaces'; +import {IsActiveNavigationItem} from './is-active-navigation-item.pipe'; + +describe('IsActiveNavigationItem', () => { + let pipe: IsActiveNavigationItem; + + beforeEach(() => { + pipe = new IsActiveNavigationItem(); + }); + + it('should return true when itemToCheck is parent of the activeItem', () => { + const result = pipe.transform(parent, activeItem); + expect(result).toBe(true); + }); + + it('should return true when itemToCheck is any kind of the ancestor of the activeItem', () => { + const result = pipe.transform(grandparent, activeItem); + expect(result).toBe(true); + }); + + it('should return false when itemToCheck is not ancestor of the activeItem', () => { + const result = pipe.transform(notRelatedItem, activeItem); + expect(result).toBe(false); + }); + + it('should return false when activeItem is null', () => { + const result = pipe.transform(notRelatedItem, null); + expect(result).toBe(false); + }); + + it('should return false when activeItem is parent of the itemToCheck', () => { + const result = pipe.transform(child, activeItem); + expect(result).toBe(false); + }); +}); + +const notRelatedItem: NavigationItem = { + label: 'Example', +}; + +const grandparent: NavigationItem = { + label: 'Grandparent', +}; + +const parent: NavigationItem = { + label: 'Parent', + parent: grandparent, +}; + +const activeItem: NavigationItem = { + label: 'Active Item', + parent: parent, +}; + +const child: NavigationItem = { + label: 'Child', + parent: activeItem, +}; diff --git a/adev/shared-docs/pipes/is-active-navigation-item.pipe.ts b/adev/shared-docs/pipes/is-active-navigation-item.pipe.ts new file mode 100644 index 000000000000..69e7d0521bde --- /dev/null +++ b/adev/shared-docs/pipes/is-active-navigation-item.pipe.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Pipe, PipeTransform} from '@angular/core'; +import {NavigationItem} from '../interfaces/index'; + +@Pipe({ + name: 'isActiveNavigationItem', + standalone: true, +}) +export class IsActiveNavigationItem implements PipeTransform { + // Check whether provided item: `itemToCheck` should be marked as active, based on `activeItem`. + // In addition to `activeItem`, we should mark all its parents, grandparents, etc. as active. + transform(itemToCheck: NavigationItem, activeItem: NavigationItem | null): boolean { + let node = activeItem?.parent; + + while (node) { + if (node === itemToCheck) { + return true; + } + + node = node.parent; + } + + return false; + } +} diff --git a/adev/shared-docs/pipes/relative-link.pipe.spec.ts b/adev/shared-docs/pipes/relative-link.pipe.spec.ts new file mode 100644 index 000000000000..38f828f25fa3 --- /dev/null +++ b/adev/shared-docs/pipes/relative-link.pipe.spec.ts @@ -0,0 +1,41 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {RelativeLink} from './relative-link.pipe'; + +describe('RelativeLink', () => { + let pipe: RelativeLink; + + beforeEach(() => { + pipe = new RelativeLink(); + }); + + it('should transform absolute url to relative', () => { + const absoluteUrl = 'https://angular.dev/guide/directives#test'; + + const result = pipe.transform(absoluteUrl); + + expect(result).toBe('guide/directives#test'); + }); + + it('should return fragment once result param is equal to `hash`', () => { + const absoluteUrl = 'https://angular.dev/guide/directives#test'; + + const result = pipe.transform(absoluteUrl, 'hash'); + + expect(result).toBe('test'); + }); + + it('should return relative url without fragment once result param is equal to `pathname`', () => { + const absoluteUrl = 'https://angular.dev/guide/directives#test'; + + const result = pipe.transform(absoluteUrl, 'pathname'); + + expect(result).toBe('guide/directives'); + }); +}); diff --git a/adev/shared-docs/pipes/relative-link.pipe.ts b/adev/shared-docs/pipes/relative-link.pipe.ts new file mode 100644 index 000000000000..8e11ed699767 --- /dev/null +++ b/adev/shared-docs/pipes/relative-link.pipe.ts @@ -0,0 +1,28 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Pipe, PipeTransform} from '@angular/core'; +import {normalizePath, removeTrailingSlash} from '../utils/index'; + +@Pipe({ + name: 'relativeLink', + standalone: true, +}) +export class RelativeLink implements PipeTransform { + transform(absoluteUrl: string, result: 'relative' | 'pathname' | 'hash' = 'relative'): string { + const url = new URL(normalizePath(absoluteUrl)); + + if (result === 'hash') { + return url.hash?.substring(1) ?? ''; + } + if (result === 'pathname') { + return `${removeTrailingSlash(normalizePath(url.pathname))}`; + } + return `${removeTrailingSlash(normalizePath(url.pathname))}${url.hash ?? ''}`; + } +} diff --git a/adev/shared-docs/providers/BUILD.bazel b/adev/shared-docs/providers/BUILD.bazel new file mode 100644 index 000000000000..c1711540b22e --- /dev/null +++ b/adev/shared-docs/providers/BUILD.bazel @@ -0,0 +1,33 @@ +load("//tools:defaults.bzl", "ng_module", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "providers", + srcs = [ + "index.ts", + ], + visibility = ["//adev/shared-docs:__subpackages__"], + deps = [ + ":lib", + ], +) + +ng_module( + name = "lib", + srcs = glob( + [ + "**/*.ts", + ], + exclude = [ + "index.ts", + "**/*.spec.ts", + ], + ), + deps = [ + "//adev/shared-docs/interfaces", + "//packages/common", + "//packages/core", + "//packages/router", + ], +) diff --git a/adev/shared-docs/providers/docs-content-loader.ts b/adev/shared-docs/providers/docs-content-loader.ts new file mode 100644 index 000000000000..a11e3b7fff0f --- /dev/null +++ b/adev/shared-docs/providers/docs-content-loader.ts @@ -0,0 +1,17 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {InjectionToken, inject} from '@angular/core'; +import {ResolveFn} from '@angular/router'; +import {DocContent, DocsContentLoader} from '../interfaces/index'; + +export const DOCS_CONTENT_LOADER = new InjectionToken('DOCS_CONTENT_LOADER'); + +export function contentResolver(contentPath: string): ResolveFn { + return () => inject(DOCS_CONTENT_LOADER).getContent(contentPath); +} diff --git a/adev/shared-docs/providers/environment.ts b/adev/shared-docs/providers/environment.ts new file mode 100644 index 000000000000..09b3a5b14077 --- /dev/null +++ b/adev/shared-docs/providers/environment.ts @@ -0,0 +1,12 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {InjectionToken} from '@angular/core'; +import {Environment} from '../interfaces/index'; + +export const ENVIRONMENT = new InjectionToken('ENVIRONMENT'); diff --git a/adev/shared-docs/providers/example-viewer-content-loader.ts b/adev/shared-docs/providers/example-viewer-content-loader.ts new file mode 100644 index 000000000000..3028f7d31d3f --- /dev/null +++ b/adev/shared-docs/providers/example-viewer-content-loader.ts @@ -0,0 +1,14 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {InjectionToken} from '@angular/core'; +import {ExampleViewerContentLoader} from '../interfaces/index'; + +export const EXAMPLE_VIEWER_CONTENT_LOADER = new InjectionToken( + 'EXAMPLE_VIEWER_CONTENT_LOADER', +); diff --git a/adev/shared-docs/providers/index.ts b/adev/shared-docs/providers/index.ts new file mode 100644 index 000000000000..a9b4a001685f --- /dev/null +++ b/adev/shared-docs/providers/index.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './docs-content-loader'; +export * from './environment'; +export * from './example-viewer-content-loader'; +export * from './is-search-dialog-open'; +export * from './local-storage'; +export * from './previews-components'; +export * from './window'; diff --git a/adev/shared-docs/providers/is-search-dialog-open.ts b/adev/shared-docs/providers/is-search-dialog-open.ts new file mode 100644 index 000000000000..ccf2ff1a6aac --- /dev/null +++ b/adev/shared-docs/providers/is-search-dialog-open.ts @@ -0,0 +1,14 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {InjectionToken, signal} from '@angular/core'; + +export const IS_SEARCH_DIALOG_OPEN = new InjectionToken('', { + providedIn: 'root', + factory: () => signal(false), +}); diff --git a/adev/shared-docs/providers/local-storage.ts b/adev/shared-docs/providers/local-storage.ts new file mode 100644 index 000000000000..c5ac69e5cd31 --- /dev/null +++ b/adev/shared-docs/providers/local-storage.ts @@ -0,0 +1,68 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {isPlatformBrowser} from '@angular/common'; +import {InjectionToken, PLATFORM_ID, inject} from '@angular/core'; + +export const LOCAL_STORAGE = new InjectionToken('LOCAL_STORAGE', { + providedIn: 'root', + factory: () => getStorage(inject(PLATFORM_ID)), +}); + +const getStorage = (platformId: Object): Storage | null => { + // Prerendering: localStorage is undefined for prerender build + return isPlatformBrowser(platformId) ? new LocalStorage() : null; +}; + +/** + * LocalStorage is wrapper class for localStorage, operations can fail due to various reasons, + * such as browser restrictions or storage limits being exceeded. A wrapper is providing error handling. + */ +class LocalStorage implements Storage { + get length(): number { + try { + return localStorage.length; + } catch { + return 0; + } + } + + clear(): void { + try { + localStorage.clear(); + } catch {} + } + + getItem(key: string): string | null { + try { + return localStorage.getItem(key); + } catch { + return null; + } + } + + key(index: number): string | null { + try { + return localStorage.key(index); + } catch { + return null; + } + } + + removeItem(key: string): void { + try { + localStorage.removeItem(key); + } catch {} + } + + setItem(key: string, value: string): void { + try { + localStorage.setItem(key, value); + } catch {} + } +} diff --git a/adev/shared-docs/providers/previews-components.ts b/adev/shared-docs/providers/previews-components.ts new file mode 100644 index 000000000000..aea833454b64 --- /dev/null +++ b/adev/shared-docs/providers/previews-components.ts @@ -0,0 +1,12 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {InjectionToken} from '@angular/core'; +import {CodeExamplesMap} from '../interfaces/index'; + +export const PREVIEWS_COMPONENTS = new InjectionToken('PREVIEWS_COMPONENTS'); diff --git a/adev/shared-docs/providers/window.ts b/adev/shared-docs/providers/window.ts new file mode 100644 index 000000000000..155bca31ce2c --- /dev/null +++ b/adev/shared-docs/providers/window.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {InjectionToken} from '@angular/core'; + +// Providing window using injection token could increase testability and portability (i.e SSR don't have a real browser environment). +export const WINDOW = new InjectionToken('WINDOW'); + +// The project uses prerendering, to resolve issue: 'window is not defined', we should get window from DOCUMENT. +// As it is recommended here: https://github.com/angular/universal/blob/main/docs/gotchas.md#strategy-1-injection +export function windowProvider(document: Document) { + return document.defaultView; +} diff --git a/adev/shared-docs/services/BUILD.bazel b/adev/shared-docs/services/BUILD.bazel new file mode 100644 index 000000000000..f4d0819533e4 --- /dev/null +++ b/adev/shared-docs/services/BUILD.bazel @@ -0,0 +1,60 @@ +load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "services", + srcs = [ + "index.ts", + ], + visibility = ["//adev/shared-docs:__subpackages__"], + deps = [ + ":lib", + ], +) + +ng_module( + name = "lib", + srcs = glob( + [ + "**/*.ts", + ], + exclude = [ + "index.ts", + "**/*.spec.ts", + ], + ), + deps = [ + "//adev/shared-docs/constants", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/providers", + "//adev/shared-docs/utils", + "//packages/common", + "//packages/core", + "//packages/core/rxjs-interop", + "//packages/router", + "@npm//algoliasearch", + "@npm//rxjs", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + deps = [ + ":lib", + "//adev/shared-docs/interfaces", + "//adev/shared-docs/providers", + "//packages/common", + "//packages/core", + "//packages/core/testing", + ], +) + +karma_web_test_suite( + name = "test", + deps = [":test_lib"], +) diff --git a/adev/shared-docs/services/index.ts b/adev/shared-docs/services/index.ts new file mode 100644 index 000000000000..0d08b34b0e69 --- /dev/null +++ b/adev/shared-docs/services/index.ts @@ -0,0 +1,13 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './navigation-state.service'; +export {TOC_SKIP_CONTENT_MARKER, TableOfContentsLoader} from './table-of-contents-loader.service'; +export {TableOfContentsScrollSpy} from './table-of-contents-scroll-spy.service'; +export * from './search.service'; +export * from './inject-async.service'; diff --git a/adev/shared-docs/services/inject-async.service.ts b/adev/shared-docs/services/inject-async.service.ts new file mode 100644 index 000000000000..88ca3a6787a0 --- /dev/null +++ b/adev/shared-docs/services/inject-async.service.ts @@ -0,0 +1,92 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + DestroyRef, + ENVIRONMENT_INITIALIZER, + EnvironmentInjector, + Injectable, + Injector, + Provider, + ProviderToken, + Type, + createEnvironmentInjector, + inject, +} from '@angular/core'; + +/** + * Convience method for lazy loading an injection token. + */ +export async function injectAsync( + injector: Injector, + providerLoader: () => Promise>, +): Promise { + const injectImpl = injector.get(InjectAsyncImpl); + return injectImpl.get(injector, providerLoader); +} + +@Injectable({providedIn: 'root'}) +class InjectAsyncImpl { + private overrides = new WeakMap(); // no need to cleanup + override(type: Type, mock: Type) { + this.overrides.set(type, mock); + } + + async get(injector: Injector, providerLoader: () => Promise>): Promise { + const type = await providerLoader(); + + // Check if we have overrides, O(1), low overhead + if (this.overrides.has(type)) { + const override = this.overrides.get(type); + return new override(); + } + + if (!(injector instanceof EnvironmentInjector)) { + // this is the DestroyRef of the component + const destroyRef = injector.get(DestroyRef); + + // This is the parent injector of the injector we're creating + const environmentInjector = injector.get(EnvironmentInjector); + + // Creating an environment injector to destroy it afterwards + const newInjector = createEnvironmentInjector([type as Provider], environmentInjector); + + // Destroy the injector to trigger DestroyRef.onDestroy on our service + destroyRef.onDestroy(() => { + newInjector.destroy(); + }); + + // We want to create the new instance of our service with our new injector + injector = newInjector; + } + + return injector.get(type)!; + } +} + +/** + * Helper function to mock the lazy loaded module in `injectAsync` + * + * @usage + * TestBed.configureTestingModule({ + * providers: [ + * mockAsyncProvider(SandboxService, fakeSandboxService) + * ] + * }); + */ +export function mockAsyncProvider(type: Type, mock: Type) { + return [ + { + provide: ENVIRONMENT_INITIALIZER, + multi: true, + useValue: () => { + inject(InjectAsyncImpl).override(type, mock); + }, + }, + ]; +} diff --git a/adev/shared-docs/services/navigation-state.service.ts b/adev/shared-docs/services/navigation-state.service.ts new file mode 100644 index 000000000000..5bcebe3b339b --- /dev/null +++ b/adev/shared-docs/services/navigation-state.service.ts @@ -0,0 +1,115 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Injectable, inject, signal} from '@angular/core'; +import {NavigationItem} from '../interfaces/index'; +import {Router} from '@angular/router'; + +@Injectable({ + providedIn: 'root', +}) +export class NavigationState { + private readonly router = inject(Router); + + private readonly _activeNavigationItem = signal(null); + private readonly _expandedItems = signal([]); + private readonly _isMobileNavVisible = signal(false); + + primaryActiveRouteItem = signal(null); + activeNavigationItem = this._activeNavigationItem.asReadonly(); + expandedItems = this._expandedItems.asReadonly(); + isMobileNavVisible = this._isMobileNavVisible.asReadonly(); + + async toggleItem(item: NavigationItem): Promise { + if (!item.children) { + return; + } + + if (item.isExpanded) { + this.collapse(item); + } else if (item.children && item.children.length > 0 && item.children[0].path) { + // It resolves false, when the user has displayed the page, then changed the slide to a secondary navigation component + // and wants to reopen the slide, where the first item is the currently displayed page + const navigationSucceeds = await this.navigateToFirstPageOfTheCategory(item.children[0].path); + + if (!navigationSucceeds) { + this.expand(item); + } + } + } + + cleanExpandedState(): void { + this._expandedItems.set([]); + } + + expandItemHierarchy( + item: NavigationItem, + shouldExpand: (item: NavigationItem) => boolean, + skipExpandPredicateFn?: (item: NavigationItem) => boolean, + ): void { + if (skipExpandPredicateFn && skipExpandPredicateFn(item)) { + // When `skipExpandPredicateFn` returns `true` then we should trigger `cleanExpandedState` + // to be sure that first navigation slide will be displayed. + this.cleanExpandedState(); + return; + } + // Returns item when parent node was already expanded + const parentItem = this._expandedItems().find( + (expandedItem) => + item.parent?.label === expandedItem.label && item.parent?.path === expandedItem.path, + ); + + if (parentItem) { + // If the parent item is expanded, then we should display all expanded items up to the active item level. + // This provides us with an appropriate list of expanded elements also when the user navigates using browser buttons. + this._expandedItems.update((expandedItems) => + expandedItems.filter( + (item) => + item.level !== undefined && + parentItem.level !== undefined && + item.level <= parentItem.level, + ), + ); + } else { + let itemsToExpand: NavigationItem[] = []; + + let node = item.parent; + + while (node && shouldExpand(node)) { + itemsToExpand.push({...node, isExpanded: true}); + node = node.parent; + } + + this._expandedItems.set(itemsToExpand.reverse()); + } + } + + setActiveNavigationItem(item: NavigationItem | null): void { + this._activeNavigationItem.set(item); + } + + setMobileNavigationListVisibility(isVisible: boolean): void { + this._isMobileNavVisible.set(isVisible); + } + + private expand(item: NavigationItem): void { + // Add item to the expanded items list + this._expandedItems.update((expandedItems) => { + return [...(expandedItems ?? []), {...item, isExpanded: true}]; + }); + } + + private collapse(item: NavigationItem): void { + item.isExpanded = false; + this._expandedItems.update((expandedItems) => expandedItems.slice(0, -1)); + } + + private async navigateToFirstPageOfTheCategory(path: string): Promise { + return this.router.navigateByUrl(path); + } +} diff --git a/adev/shared-docs/services/search.service.ts b/adev/shared-docs/services/search.service.ts new file mode 100644 index 000000000000..d0ae5a2b0e85 --- /dev/null +++ b/adev/shared-docs/services/search.service.ts @@ -0,0 +1,88 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Injectable, afterNextRender, inject, signal} from '@angular/core'; +import {ENVIRONMENT} from '../providers/index'; +import {SearchResult} from '../interfaces/index'; +import {toObservable} from '@angular/core/rxjs-interop'; +import {debounceTime, filter, from, of, switchMap} from 'rxjs'; +import algoliasearch, {SearchClient} from 'algoliasearch/lite'; +import {NavigationEnd, Router} from '@angular/router'; + +export const SEARCH_DELAY = 200; +// Maximum number of facet values to return for each facet during a regular search. +export const MAX_VALUE_PER_FACET = 5; + +@Injectable({ + providedIn: 'root', +}) +export class Search { + private readonly _searchQuery = signal(''); + private readonly _searchResults = signal(undefined); + + private readonly router = inject(Router); + private readonly config = inject(ENVIRONMENT); + private readonly client: SearchClient = (algoliasearch as any)( + this.config.algolia.appId, + this.config.algolia.apiKey, + ); + private readonly index = this.client.initIndex(this.config.algolia.indexName); + + searchQuery = this._searchQuery.asReadonly(); + searchResults = this._searchResults.asReadonly(); + + searchResults$ = toObservable(this.searchQuery).pipe( + debounceTime(SEARCH_DELAY), + switchMap((query) => { + return !!query + ? from( + this.index.search(query, { + maxValuesPerFacet: MAX_VALUE_PER_FACET, + }), + ) + : of(undefined); + }), + ); + + constructor() { + afterNextRender(() => { + this.listenToSearchResults(); + this.resetSearchQueryOnNavigationEnd(); + }); + } + + updateSearchQuery(query: string): void { + this._searchQuery.set(query); + } + + private listenToSearchResults(): void { + this.searchResults$.subscribe((response: any) => { + this._searchResults.set( + response ? this.getUniqueSearchResultItems(response.hits) : undefined, + ); + }); + } + + private getUniqueSearchResultItems(items: SearchResult[]): SearchResult[] { + const uniqueUrls = new Set(); + + return items.filter((item) => { + if (item.url && !uniqueUrls.has(item.url)) { + uniqueUrls.add(item.url); + return true; + } + return false; + }); + } + + private resetSearchQueryOnNavigationEnd(): void { + this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => { + this.updateSearchQuery(''); + }); + } +} diff --git a/adev/shared-docs/services/table-of-contents-loader.service.spec.ts b/adev/shared-docs/services/table-of-contents-loader.service.spec.ts new file mode 100644 index 000000000000..a1b33704b875 --- /dev/null +++ b/adev/shared-docs/services/table-of-contents-loader.service.spec.ts @@ -0,0 +1,118 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {TestBed} from '@angular/core/testing'; + +import {TOC_SKIP_CONTENT_MARKER, TableOfContentsLoader} from './table-of-contents-loader.service'; + +describe('TableOfContentsLoader', () => { + let service: TableOfContentsLoader; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [TableOfContentsLoader], + }); + service = TestBed.inject(TableOfContentsLoader); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should create empty table of content list when there is no headings in content', () => { + const element = createElement('element-without-headings'); + + service.buildTableOfContent(element); + + expect(service.tableOfContentItems()).toEqual([]); + }); + + it('should create empty table of content list when there is only h1 elements', () => { + const element = createElement('element-with-only-h1'); + + service.buildTableOfContent(element); + + expect(service.tableOfContentItems()).toEqual([]); + }); + + it('should create table of content list with h2 and h3 when h2 and h3 headings exists', () => { + const element = createElement('element-with-h1-h2-h3-h4'); + + service.buildTableOfContent(element); + + expect(service.tableOfContentItems().length).toEqual(5); + expect(service.tableOfContentItems()[0].id).toBe('item-2'); + expect(service.tableOfContentItems()[1].id).toBe('item-3'); + expect(service.tableOfContentItems()[2].id).toBe('item-5'); + expect(service.tableOfContentItems()[3].id).toBe('item-6'); + expect(service.tableOfContentItems()[4].id).toBe('item-7'); + + expect(service.tableOfContentItems()[0].level).toBe('h2'); + expect(service.tableOfContentItems()[1].level).toBe('h3'); + expect(service.tableOfContentItems()[2].level).toBe('h3'); + expect(service.tableOfContentItems()[3].level).toBe('h2'); + expect(service.tableOfContentItems()[4].level).toBe('h3'); + + expect(service.tableOfContentItems()[0].title).toBe('H2 - first'); + expect(service.tableOfContentItems()[1].title).toBe('H3 - first'); + expect(service.tableOfContentItems()[2].title).toBe('H3 - second'); + expect(service.tableOfContentItems()[3].title).toBe('H2 - second'); + expect(service.tableOfContentItems()[4].title).toBe('H3 - third'); + }); + + it('should not display in ToC h2,h3 without ids', () => { + const element = createElement('element-with-h2-h3-without-id'); + + service.buildTableOfContent(element); + + expect(service.tableOfContentItems().length).toBe(0); + }); + + it('should not display in ToC h2,h3 which are childrens of docs-example-viewer', () => { + const element = createElement('headings-inside-docs-example-viewer'); + + service.buildTableOfContent(element); + + expect(service.tableOfContentItems().length).toBe(0); + }); + + it(`should not display in ToC heading with ${TOC_SKIP_CONTENT_MARKER} attr`, () => { + const element = createElement('headings-inside-toc-skip-content'); + + service.buildTableOfContent(element); + + expect(service.tableOfContentItems().length).toBe(1); + expect(service.tableOfContentItems()[0].id).toBe('item-1'); + }); +}); + +function createElement(id: string): HTMLElement { + const div = document.createElement('div'); + div.innerHTML = fakeElementHtml[id]; + return div; +} + +const fakeHeadings = `

H1

+

H2 - first

+

H3 - first

+

H4

+

H3 - second

+

H2 - second

+

H3 - third

`; + +const fakeElementHtml: Record = { + 'element-without-headings': `
content
`, + 'element-with-only-h1': `

heading

`, + 'element-with-h1-h2-h3-h4': `
+ ${fakeHeadings} +
+ `, + 'element-with-h2-h3-without-id': `

heading

heading

`, + 'headings-inside-docs-example-viewer': `${fakeHeadings}`, + 'headings-inside-toc-skip-content': `

heading

skip heading

`, +}; diff --git a/adev/shared-docs/services/table-of-contents-loader.service.ts b/adev/shared-docs/services/table-of-contents-loader.service.ts new file mode 100644 index 000000000000..40b76424ff6a --- /dev/null +++ b/adev/shared-docs/services/table-of-contents-loader.service.ts @@ -0,0 +1,91 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {DOCUMENT, isPlatformBrowser} from '@angular/common'; +import {inject, signal, Injectable, PLATFORM_ID} from '@angular/core'; + +import {TableOfContentsItem, TableOfContentsLevel} from '../interfaces/index'; + +/** + * Name of an attribute that is set on an element that should be + * excluded from the `TableOfContentsLoader` lookup. This is needed + * to exempt SSR'ed content of the `TableOfContents` component from + * being inspected and accidentally pulling more content into ToC. + */ +export const TOC_SKIP_CONTENT_MARKER = 'toc-skip-content'; + +@Injectable({providedIn: 'root'}) +export class TableOfContentsLoader { + // There are some cases when default browser anchor scrolls a little above the + // heading In that cases wrong item was selected. The value found by trial and + // error. + readonly toleranceThreshold = 40; + + readonly tableOfContentItems = signal([] as TableOfContentsItem[]); + + private readonly document = inject(DOCUMENT); + private readonly platformId = inject(PLATFORM_ID); + + buildTableOfContent(docElement: Element): void { + const headings = this.getHeadings(docElement); + const tocList: TableOfContentsItem[] = headings.map((heading) => ({ + id: heading.id, + level: heading.tagName.toLowerCase() as TableOfContentsLevel, + title: this.getHeadingTitle(heading), + top: this.calculateTop(heading), + })); + + this.tableOfContentItems.set(tocList); + } + + // Update top value of heading, it should be executed after window resize + updateHeadingsTopValue(element: HTMLElement): void { + const headings = this.getHeadings(element); + const updatedTopValues = new Map(); + + for (const heading of headings) { + const parentTop = heading.parentElement?.offsetTop ?? 0; + const top = Math.floor(parentTop + heading.offsetTop - this.toleranceThreshold); + updatedTopValues.set(heading.id, top); + } + + this.tableOfContentItems.update((oldItems) => { + let newItems = [...oldItems]; + for (const item of newItems) { + item.top = updatedTopValues.get(item.id) ?? 0; + } + return newItems; + }); + } + + private getHeadingTitle(heading: HTMLHeadingElement): string { + const div: HTMLDivElement = this.document.createElement('div'); + div.innerHTML = heading.innerHTML; + + return (div.textContent || '').trim(); + } + + // Get all headings (h2 and h3) with ids, which are not children of the + // docs-example-viewer component. + private getHeadings(element: Element): HTMLHeadingElement[] { + return Array.from( + element.querySelectorAll( + `h2[id]:not(docs-example-viewer h2):not([${TOC_SKIP_CONTENT_MARKER}]),` + + `h3[id]:not(docs-example-viewer h3):not([${TOC_SKIP_CONTENT_MARKER}])`, + ), + ); + } + + private calculateTop(heading: HTMLHeadingElement): number { + if (!isPlatformBrowser(this.platformId)) return 0; + return ( + Math.floor(heading.offsetTop > 0 ? heading.offsetTop : heading.getClientRects()[0]?.top) - + this.toleranceThreshold + ); + } +} diff --git a/adev/shared-docs/services/table-of-contents-scroll-spy.service.spec.ts b/adev/shared-docs/services/table-of-contents-scroll-spy.service.spec.ts new file mode 100644 index 000000000000..d4ec27cb0de4 --- /dev/null +++ b/adev/shared-docs/services/table-of-contents-scroll-spy.service.spec.ts @@ -0,0 +1,146 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {TestBed, discardPeriodicTasks, fakeAsync, tick} from '@angular/core/testing'; + +import {WINDOW} from '../providers'; + +import {TableOfContentsLoader} from './table-of-contents-loader.service'; +import {SCROLL_EVENT_DELAY, TableOfContentsScrollSpy} from './table-of-contents-scroll-spy.service'; +import {DOCUMENT, ViewportScroller} from '@angular/common'; +import {TableOfContentsLevel} from '../interfaces'; + +describe('TableOfContentsScrollSpy', () => { + let service: TableOfContentsScrollSpy; + const fakeWindow = { + addEventListener: () => {}, + removeEventListener: () => {}, + scrollY: 0, + }; + const fakeToCItems = [ + { + id: 'h2', + level: TableOfContentsLevel.H2, + title: 'h2', + top: 100, + }, + { + id: 'first', + level: TableOfContentsLevel.H3, + title: 'first', + top: 400, + }, + { + id: 'second', + level: TableOfContentsLevel.H3, + title: 'second', + top: 900, + }, + { + id: 'third', + level: TableOfContentsLevel.H3, + title: 'third', + top: 1200, + }, + { + id: 'fourth', + level: TableOfContentsLevel.H3, + title: 'fourth', + top: 1900, + }, + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + TableOfContentsScrollSpy, + { + provide: WINDOW, + useValue: fakeWindow, + }, + ], + }); + const tableOfContentsLoaderSpy = TestBed.inject(TableOfContentsLoader); + tableOfContentsLoaderSpy.tableOfContentItems.set(fakeToCItems); + service = TestBed.inject(TableOfContentsScrollSpy); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should activeItemId be null by default', () => { + expect(service.activeItemId()).toBeNull(); + }); + + it('should call scrollToPosition([0, 0]) once scrollToTop was invoked', () => { + const scrollToPositionSpy = spyOn( + service['viewportScroller'], + 'scrollToPosition', + ); + + service.scrollToTop(); + + expect(scrollToPositionSpy).toHaveBeenCalledOnceWith([0, 0]); + }); + + it(`should only fire setActiveItemId every ${SCROLL_EVENT_DELAY}ms when scrolling`, fakeAsync(() => { + const doc = TestBed.inject(DOCUMENT); + const scrollableContainer = doc; + const setActiveItemIdSpy = spyOn(service as any, 'setActiveItemId'); + + service.startListeningToScroll(doc.querySelector('fake-selector')); + + scrollableContainer.dispatchEvent(new Event('scroll')); + tick(SCROLL_EVENT_DELAY - 2); + + expect(setActiveItemIdSpy).not.toHaveBeenCalled(); + + scrollableContainer.dispatchEvent(new Event('scroll')); + tick(1); + + expect(setActiveItemIdSpy).not.toHaveBeenCalled(); + + scrollableContainer.dispatchEvent(new Event('scroll')); + tick(1); + + expect(setActiveItemIdSpy).toHaveBeenCalled(); + + discardPeriodicTasks(); + })); + + it(`should set active item when window scrollY is greater than calculated section top value`, fakeAsync(() => { + const doc = TestBed.inject(DOCUMENT); + const scrollableContainer = doc; + + service.startListeningToScroll(doc.querySelector('fake-selector')); + + fakeWindow.scrollY = 1238; + scrollableContainer.dispatchEvent(new Event('scroll')); + tick(SCROLL_EVENT_DELAY); + + expect(service.activeItemId()).toEqual('third'); + + discardPeriodicTasks(); + })); + + it(`should set null as active item when window scrollY is lesser than the top value of the first section`, fakeAsync(() => { + const doc = TestBed.inject(DOCUMENT); + const scrollableContainer = doc; + + service.startListeningToScroll(doc.querySelector('fake-selector')); + + fakeWindow.scrollY = 99; + scrollableContainer.dispatchEvent(new Event('scroll')); + tick(SCROLL_EVENT_DELAY); + + expect(service.activeItemId()).toEqual(null); + + discardPeriodicTasks(); + })); +}); diff --git a/adev/shared-docs/services/table-of-contents-scroll-spy.service.ts b/adev/shared-docs/services/table-of-contents-scroll-spy.service.ts new file mode 100644 index 000000000000..f104d89f724f --- /dev/null +++ b/adev/shared-docs/services/table-of-contents-scroll-spy.service.ts @@ -0,0 +1,167 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {DOCUMENT, ViewportScroller} from '@angular/common'; +import { + DestroyRef, + EnvironmentInjector, + Injectable, + afterNextRender, + inject, + signal, + NgZone, +} from '@angular/core'; +import {RESIZE_EVENT_DELAY} from '../constants/index'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {auditTime, debounceTime, fromEvent, startWith} from 'rxjs'; +import {WINDOW} from '../providers/index'; +import {shouldReduceMotion} from '../utils/index'; +import {TableOfContentsLoader} from './table-of-contents-loader.service'; + +export const SCROLL_EVENT_DELAY = 20; +export const SCROLL_FINISH_DELAY = SCROLL_EVENT_DELAY * 2; + +@Injectable({providedIn: 'root'}) +// The service is responsible for listening for scrolling and resizing, +// thanks to which it sets the active item in the Table of contents +export class TableOfContentsScrollSpy { + private readonly destroyRef = inject(DestroyRef); + private readonly tableOfContentsLoader = inject(TableOfContentsLoader); + private readonly document = inject(DOCUMENT); + private readonly window = inject(WINDOW); + private readonly ngZone = inject(NgZone); + private readonly viewportScroller = inject(ViewportScroller); + private readonly injector = inject(EnvironmentInjector); + private contentSourceElement: HTMLElement | null = null; + private lastContentWidth = 0; + + activeItemId = signal(null); + scrollbarThumbOnTop = signal(true); + + startListeningToScroll(contentSourceElement: HTMLElement | null): void { + this.contentSourceElement = contentSourceElement; + this.lastContentWidth = this.getContentWidth(); + + this.setScrollEventHandlers(); + this.setResizeEventHandlers(); + } + + scrollToTop(): void { + this.viewportScroller.scrollToPosition([0, 0]); + } + + scrollToSection(id: string): void { + if (shouldReduceMotion()) { + this.offsetToSection(id); + } else { + const section = this.document.getElementById(id); + section?.scrollIntoView({behavior: 'smooth', block: 'start'}); + // We don't want to set the active item here, it would mess up the animation + // The scroll event handler will handle it for us + } + } + + private offsetToSection(id: string): void { + const section = this.document.getElementById(id); + section?.scrollIntoView({block: 'start'}); + // Here we need to set the active item manually because scroll events might not be fired + this.activeItemId.set(id); + } + + // After window resize, we should update top value of each table content item + private setResizeEventHandlers() { + fromEvent(this.window, 'resize') + .pipe(debounceTime(RESIZE_EVENT_DELAY), takeUntilDestroyed(this.destroyRef), startWith()) + .subscribe(() => { + this.updateHeadingsTopAfterResize(); + }); + + // We need to observe the height of the docs-viewer because it may change after the + // assets (fonts, images) are loaded. They can (and will) change the y-position of the headings. + const docsViewer = this.document.querySelector('docs-viewer'); + if (docsViewer) { + afterNextRender( + () => { + const resizeObserver = new ResizeObserver(() => this.updateHeadingsTopAfterResize()); + resizeObserver.observe(docsViewer); + this.destroyRef.onDestroy(() => resizeObserver.disconnect()); + }, + {injector: this.injector}, + ); + } + } + + private updateHeadingsTopAfterResize(): void { + this.lastContentWidth = this.getContentWidth(); + + const contentElement = this.contentSourceElement; + if (contentElement) { + this.tableOfContentsLoader.updateHeadingsTopValue(contentElement); + this.setActiveItemId(); + } + } + + private setScrollEventHandlers(): void { + const scroll$ = fromEvent(this.document, 'scroll').pipe( + auditTime(SCROLL_EVENT_DELAY), + takeUntilDestroyed(this.destroyRef), + ); + + this.ngZone.runOutsideAngular(() => { + scroll$.subscribe(() => this.setActiveItemId()); + }); + } + + private setActiveItemId(): void { + const tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems(); + + if (tableOfContentItems.length === 0) return; + + // Resize could emit scroll event, in that case we could stop setting active item until resize will be finished + if (this.lastContentWidth !== this.getContentWidth()) { + return; + } + + const scrollOffset = this.getScrollOffset(); + if (scrollOffset === null) return; + + for (const [i, currentLink] of tableOfContentItems.entries()) { + const nextLink = tableOfContentItems[i + 1]; + + // A link is considered active if the page is scrolled past the + // anchor without also being scrolled passed the next link. + const isActive = + scrollOffset >= currentLink.top && (!nextLink || nextLink.top >= scrollOffset); + + // When active item was changed then trigger change detection + if (isActive && this.activeItemId() !== currentLink.id) { + this.activeItemId.set(currentLink.id); + return; + } + } + + if (scrollOffset < tableOfContentItems[0].top && this.activeItemId() !== null) { + this.activeItemId.set(null); + } + + const scrollOffsetZero = scrollOffset === 0; + if (scrollOffsetZero !== this.scrollbarThumbOnTop()) { + // we want to trigger change detection only when the value changes + this.scrollbarThumbOnTop.set(scrollOffsetZero); + } + } + + // Gets the scroll offset of the scroll container + private getScrollOffset(): number { + return this.window.scrollY; + } + + private getContentWidth(): number { + return this.document.body.clientWidth || Number.MAX_SAFE_INTEGER; + } +} diff --git a/adev/shared-docs/styles/BUILD.bazel b/adev/shared-docs/styles/BUILD.bazel new file mode 100644 index 000000000000..dd4b0f072c38 --- /dev/null +++ b/adev/shared-docs/styles/BUILD.bazel @@ -0,0 +1,12 @@ +load("@io_bazel_rules_sass//:defs.bzl", "sass_library") + +package(default_visibility = ["//visibility:private"]) + +sass_library( + name = "styles", + srcs = glob(["**/*.scss"]), + visibility = [ + "//adev/shared-docs:__pkg__", + "//adev/shared-docs/components:__subpackages__", + ], +) diff --git a/adev/shared-docs/styles/_anchor.scss b/adev/shared-docs/styles/_anchor.scss new file mode 100644 index 000000000000..91bcec189f0f --- /dev/null +++ b/adev/shared-docs/styles/_anchor.scss @@ -0,0 +1,19 @@ +@mixin docs-anchor() { + &::after { + content: '\e157'; // codepoint for "link" + font-family: 'Material Symbols Outlined'; + + opacity: 0; + margin-left: 8px; + vertical-align: middle; + + color: var(--quaternary-contrast); + font-size: clamp(18px, 1.25em, 30px); + transition: opacity 0.3s ease; + } + &:hover { + &::after { + opacity: 1; + } + } +} diff --git a/adev/shared-docs/styles/_api-item-label.scss b/adev/shared-docs/styles/_api-item-label.scss new file mode 100644 index 000000000000..dfadc46b5015 --- /dev/null +++ b/adev/shared-docs/styles/_api-item-label.scss @@ -0,0 +1,102 @@ +@mixin api-item-label() { + .docs-api-item-label { + --label-theme: var(--symbolic-purple); + + display: flex; + justify-content: center; + align-items: center; + font-weight: 500; + color: var(--label-theme); + background: color-mix(in srgb, var(--label-theme) 10%, white); + border-radius: 0.25rem; + transition: + color 0.3s ease, + background-color 0.3s ease; + text-transform: capitalize; + + &[data-mode='short'] { + height: 22px; + width: 22px; + } + + &[data-mode='full'] { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + } + + a { + color: var(--label-theme); + + &:hover { + text-decoration: underline; + } + } + + @media screen and (prefers-color-scheme: dark) { + background: color-mix(in srgb, var(--label-theme) 17%, #272727); + } + + .docs-dark-mode & { + background: color-mix(in srgb, var(--label-theme) 17%, #272727); + } + + .docs-light-mode & { + background: color-mix(in srgb, var(--label-theme) 10%, white); + } + + &[data-type='undecorated_class'], + &[data-type='class'] { + --label-theme: var(--symbolic-purple); + } + + &[data-type='constant'], + &[data-type='const'] { + --label-theme: var(--symbolic-gray); + } + + &[data-type='decorator'] { + --label-theme: var(--symbolic-blue); + } + + &[data-type='directive'] { + --label-theme: var(--symbolic-pink); + } + + &[data-type='element'] { + --label-theme: var(--symbolic-orange); + } + + &[data-type='enum'] { + --label-theme: var(--symbolic-yellow); + } + + &[data-type='function'] { + --label-theme: var(--symbolic-green); + } + + &[data-type='interface'] { + --label-theme: var(--symbolic-cyan); + } + + &[data-type='pipe'] { + --label-theme: var(--symbolic-teal); + } + + &[data-type='ng_module'] { + --label-theme: var(--symbolic-brown); + } + + &[data-type='type_alias'] { + --label-theme: var(--symbolic-lime); + } + + &[data-type='block'] { + --label-theme: var(--vivid-pink); + } + + &[data-type='developer_preview'], + &[data-type='deprecated'] { + --label-theme: var(--hot-red); + } + } +} diff --git a/adev/shared-docs/styles/_button.scss b/adev/shared-docs/styles/_button.scss new file mode 100644 index 000000000000..6f3b04ebe14c --- /dev/null +++ b/adev/shared-docs/styles/_button.scss @@ -0,0 +1,148 @@ +@mixin button() { + button { + font-family: var(--inter-font); + background: transparent; + -webkit-appearance: none; + border: 0; + font-weight: 600; + + // Remove excess padding and border in Firefox 4+ + &::-moz-focus-inner { + border: 0; + padding: 0; + } + + &:disabled { + cursor: not-allowed; + } + } + + @property --angle { + syntax: ''; + initial-value: 90deg; + inherits: false; + } + + @keyframes spin-gradient { + 0% { + --angle: 90deg; + } + 100% { + --angle: 450deg; + } + } + + .docs-primary-btn { + cursor: pointer; + border: none; + outline: none; + position: relative; + border-radius: 0.25rem; + padding: 0.75rem 1.5rem; + width: max-content; + color: transparent; + + // border gradient / background + --angle: 90deg; + background: linear-gradient( + var(--angle), + var(--orange-red) 0%, + var(--vivid-pink) 50%, + var(--electric-violet) 100% + ); + + docs-icon { + z-index: var(--z-index-content); + position: relative; + } + + // text & radial gradient + &::before { + content: attr(text); + position: absolute; + inset: 1px; + background: var(--page-bg-radial-gradient); + border-radius: 0.2rem; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.3s ease, background 0.3s ease; + color: var(--primary-contrast); + } + + // solid color negative space - CSS transition supported + &::after { + content: attr(text); + position: absolute; + inset: 1px; + background: var(--page-background); + border-radius: 0.2rem; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.3s ease, background 0.3s ease; + color: var(--primary-contrast); + } + + &:hover { + animation: spin-gradient 4s linear infinite forwards; + &::before { + background-color: var(--page-background); + background: var(--soft-pink-radial-gradient); + opacity: 0.9; + } + &::after { + opacity: 0; + } + } + + &:active { + &::before { + opacity: 0.8; + } + } + + &:disabled { + //gradient stroke + background: var(--quinary-contrast); + color: var(--quinary-contrast); + + &::before { + background-color: var(--page-background); + background: var(--page-bg-radial-gradient); + opacity: 1; + } + + docs-icon { + color: var(--quinary-contrast); + } + } + + docs-icon { + z-index: var(--z-index-icon); + color: var(--primary-contrast); + } + } + + .docs-secondary-btn { + border: 1px solid var(--senary-contrast); + background: var(--page-background); + padding: 0.75rem 1.5rem; + border-radius: 0.25rem; + color: var(--primary-contrast); + transition: background 0.3s ease; + + docs-icon { + color: var(--quaternary-contrast); + transition: color 0.3s ease; + } + + &:hover { + background: var(--septenary-contrast); + + docs-icon { + color: var(--primary-contrast); + } + } + } +} diff --git a/adev/shared-docs/styles/_colors.scss b/adev/shared-docs/styles/_colors.scss new file mode 100644 index 000000000000..019b18b1833f --- /dev/null +++ b/adev/shared-docs/styles/_colors.scss @@ -0,0 +1,297 @@ +// Colors +// Using OKLCH color space for better color reproduction on P3 displays, +// as well as better human-readability +// --> https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch + +@mixin root-definitions() { + // PRIMITIVES + // Colors + --bright-blue: oklch(51.01% 0.274 263.83); // #0546ff + --indigo-blue: oklch(51.64% 0.229 281.65); // #5c44e4 + --electric-violet: oklch(53.18% 0.28 296.97); // #8514f5 + --french-violet: oklch(47.66% 0.246 305.88); // #8001c6 + --vivid-pink: oklch(69.02% 0.277 332.77); // #f637e3 + --hot-pink: oklch(59.91% 0.239 8.14); // #e90464 + --hot-red: oklch(61.42% 0.238 15.34); // #f11653 + --orange-red: oklch(63.32% 0.24 31.68); // #fa2c04 + --super-green: oklch(79.12% 0.257 155.13); // #00c572 // Used for success, merge additions, etc. + + // subtle-purple is used for inline-code bg, docs-card hover bg & docs-code header bg + --subtle-purple: color-mix(in srgb, var(--bright-blue) 5%, white 10%); + --light-blue: color-mix(in srgb, var(--bright-blue), white 50%); + --light-violet: color-mix(in srgb, var(--electric-violet), white 65%); + --light-orange: color-mix(in srgb, var(--orange-red), white 50%); + --light-pink: color-mix(in srgb, var(--vivid-pink) 10%, white 80%); + + // SYMBOLIC COLORS + // Used for Type Labels + --symbolic-purple: oklch(42.86% 0.29 266.4); //#1801ea + --symbolic-gray: oklch(66.98% 0 0); // #959595 + --symbolic-blue: oklch(42.45% 0.223 263.38); // #0037c5; + --symbolic-pink: oklch(63.67% 0.254 13.47); // #ff025c + --symbolic-orange: oklch(64.73% 0.23769984683784018 33.18328352127882); // #fe3700 + --symbolic-yellow: oklch(78.09% 0.163 65.69); // #fd9f28 + --symbolic-green: oklch(67.83% 0.229 142.73); // #00b80a + --symbolic-cyan: oklch(67.05% 0.1205924489987394 181.34025902203868); // #00ad9a + --symbolic-magenta: oklch(51.74% 0.25453048882711515 315.26261625862725); // #9c00c8 + --symbolic-teal: oklch(57.59% 0.083 230.58); // #3f82a1 + --symbolic-brown: oklch(49.06% 0.128 46.41); // #994411 + --symbolic-lime: oklch(70.33% 0.2078857836035299 135.66843631046476); // #5dba00 + + // Grays + --gray-1000: oklch(16.93% 0.004 285.95); // #0f0f11 + --gray-900: oklch(19.37% 0.006 300.98); // #151417 + --gray-800: oklch(25.16% 0.008 308.11); // #232125 + --gray-700: oklch(36.98% 0.014 302.71); // #413e46 + --gray-600: oklch(44% 0.019 306.08); // #55505b + --gray-500: oklch(54.84% 0.023 304.99); // #746e7c + --gray-400: oklch(70.9% 0.015 304.04); // #a39fa9 + --gray-300: oklch(84.01% 0.009 308.34); // #ccc9cf + --gray-200: oklch(91.75% 0.004 301.42); // #e4e3e6 + --gray-100: oklch(97.12% 0.002 325.59); // #f6f5f6 + --gray-50: oklch(98.81% 0 0); // #fbfbfb + + // GRADIENTS + --red-to-pink-horizontal-gradient: linear-gradient( + 90deg, + var(--hot-pink) 11.42%, + var(--hot-red) 34.83%, + var(--vivid-pink) 60.69% + ); + + --red-to-pink-to-purple-horizontal-gradient: linear-gradient( + 90deg, + var(--orange-red) 0%, + var(--vivid-pink) 50%, + var(--electric-violet) 100% + ); + + --pink-to-highlight-to-purple-to-blue-horizontal-gradient: linear-gradient( + 140deg, + var(--vivid-pink) 0%, + var(--vivid-pink) 15%, + color-mix(in srgb, var(--vivid-pink), var(--electric-violet) 50%) 25%, + color-mix(in srgb, var(--vivid-pink), var(--electric-violet) 10%) 35%, + color-mix(in srgb, var(--vivid-pink), var(--orange-red) 50%) 42%, + color-mix(in srgb, var(--vivid-pink), var(--orange-red) 50%) 44%, + color-mix(in srgb, var(--vivid-pink), var(--page-background) 70%) 47%, + var(--electric-violet) 48%, + var(--bright-blue) 60% + ); + + --purple-to-blue-horizontal-gradient: linear-gradient( + 90deg, + var(--electric-violet) 0%, + var(--bright-blue) 100% + ); + --purple-to-blue-vertical-gradient: linear-gradient( + 0deg, + var(--electric-violet) 0%, + var(--bright-blue) 100% + ); + + --red-to-orange-horizontal-gradient: linear-gradient( + 90deg, + var(--hot-pink) 0%, + var(--orange-red) 100% + ); + --red-to-orange-vertical-gradient: linear-gradient( + 0deg, + var(--hot-pink) 0%, + var(--orange-red) 100% + ); + + --pink-to-purple-horizontal-gradient: linear-gradient( + 90deg, + var(--vivid-pink) 0%, + var(--electric-violet) 100% + ); + --pink-to-purple-vertical-gradient: linear-gradient( + 0deg, + var(--electric-violet) 0%, + var(--vivid-pink) 100% + ); + + --purple-to-light-purple-vertical-gradient: linear-gradient( + 0deg, + var(--french-violet) 0%, + var(--light-violet) 100% + ); + + --green-to-cyan-vertical-gradient: linear-gradient( + 0deg, + var(--symbolic-cyan) 0%, + var(--super-green) 100% + ); + + --blue-to-teal-vertical-gradient: linear-gradient( + 0deg, + var(--bright-blue) 0%, + var(--light-blue) 100% + ); + + --blue-to-cyan-vertical-gradient: linear-gradient( + 0deg, + var(--bright-blue) 0%, + var(--symbolic-cyan) 100% + ); + + --black-to-gray-vertical-gradient: linear-gradient( + 0deg, + var(--primary-contrast) 0%, + var(--gray-400) 100% + ); + + --red-to-pink-vertical-gradient: linear-gradient(0deg, var(--hot-red) 0%, var(--vivid-pink) 100%); + --orange-to-pink-vertical-gradient: linear-gradient( + 0deg, + var(--vivid-pink) 0%, + var(--light-orange) 100% + ); + + // Radial Gradients + --page-bg-radial-gradient: radial-gradient(circle, white 0%, white 100%); + --soft-pink-radial-gradient: radial-gradient( + circle at center bottom, + var(--light-pink) 0%, + white 80% + ); + + // ABSTRACTIONS light - dark + // --full-contrast: black - white + // --primary-constrast: gray-900 - gray-100 + // --secondary-contrast: gray-800 - gray-300 + // --tertiary-contrast: gray-700 - gray-300 + // --quaternary-contrast: gray-500 - gray-400 + // --quinary-contrast: gray-300 - gray-500 + // --senary-contrast: gray-200 - gray-700 + // --septenary-contrast: gray-100 - gray-800 + // --octonary-contrast: gray-50 - gray-900 + // --page-background white - gray-1000 + + // LIGHT MODE is default + // contrast - light mode + --full-contrast: black; + --primary-contrast: var(--gray-900); + --secondary-contrast: var(--gray-800); + --tertiary-contrast: var(--gray-700); + --quaternary-contrast: var(--gray-500); + --quinary-contrast: var(--gray-300); + --senary-contrast: var(--gray-200); + --septenary-contrast: var(--gray-100); + --octonary-contrast: var(--gray-50); + --page-background: white; + + // Home page + // for the "unfilled" portion of the word that hasn't + // been highlighted by the gradient + --gray-unfilled: var(--gray-400); + // TODO: convert oklch to hex at build time + --webgl-page-background: #ffffff; + --webgl-gray-unfilled: #a39fa9; +} + +@mixin dark-mode-definitions() { + // Contrasts + --full-contrast: white; + --primary-contrast: var(--gray-50); + --secondary-contrast: var(--gray-300); + --tertiary-contrast: var(--gray-300); + --quaternary-contrast: var(--gray-400); + --quinary-contrast: var(--gray-500); + --senary-contrast: var(--gray-700); + --septenary-contrast: var(--gray-800); + --octonary-contrast: var(--gray-900); + --page-background: var(--gray-1000); + + --bright-blue: color-mix(in srgb, oklch(51.01% 0.274 263.83), var(--full-contrast) 60%); + --indigo-blue: color-mix(in srgb, oklch(51.64% 0.229 281.65), var(--full-contrast) 70%); + --electric-violet: color-mix(in srgb, oklch(53.18% 0.28 296.97), var(--full-contrast) 70%); + --french-violet: color-mix(in srgb, oklch(47.66% 0.246 305.88), var(--full-contrast) 70%); + --vivid-pink: color-mix(in srgb, oklch(69.02% 0.277 332.77), var(--full-contrast) 70%); + --hot-pink: color-mix(in srgb, oklch(59.91% 0.239 8.14), var(--full-contrast) 70%); + --hot-red: color-mix(in srgb, oklch(61.42% 0.238 15.34), var(--full-contrast) 70%); + --orange-red: color-mix(in srgb, oklch(63.32% 0.24 31.68), var(--full-contrast) 60%); + --super-green: color-mix(in srgb, oklch(79.12% 0.257 155.13), var(--full-contrast) 70%); + + --light-pink: color-mix(in srgb, var(--vivid-pink) 5%, var(--page-background) 75%); + + --symbolic-purple: color-mix(in srgb, oklch(42.86% 0.29 266.4), var(--full-contrast) 65%); + --symbolic-gray: color-mix(in srgb, oklch(66.98% 0 0), var(--full-contrast) 65%); + --symbolic-blue: color-mix(in srgb, oklch(42.45% 0.223 263.38), var(--full-contrast) 65%); + --symbolic-pink: color-mix(in srgb, oklch(63.67% 0.254 13.47), var(--full-contrast) 65%); + --symbolic-orange: color-mix( + in srgb, + oklch(64.73% 0.23769984683784018 33.18328352127882), + var(--full-contrast) 65% + ); + --symbolic-yellow: color-mix(in srgb, oklch(78.09% 0.163 65.69), var(--full-contrast) 65%); + --symbolic-green: color-mix(in srgb, oklch(67.83% 0.229 142.73), var(--full-contrast) 65%); + --symbolic-cyan: color-mix( + in srgb, + oklch(67.05% 0.1205924489987394 181.34025902203868), + var(--full-contrast) 65% + ); + --symbolic-magenta: color-mix( + in srgb, + oklch(51.74% 0.25453048882711515 315.26261625862725), + var(--full-contrast) 65% + ); + --symbolic-teal: color-mix(in srgb, oklch(57.59% 0.083 230.58), var(--full-contrast) 65%); + --symbolic-brown: color-mix(in srgb, oklch(49.06% 0.128 46.41), var(--full-contrast) 65%); + --symbolic-lime: color-mix( + in srgb, + oklch(70.33% 0.2078857836035299 135.66843631046476), + var(--full-contrast) 65% + ); + + --page-bg-radial-gradient: radial-gradient(circle, black 0%, black 100%); + --soft-pink-radial-gradient: radial-gradient( + circle at center bottom, + var(--light-pink) 0%, + color-mix(in srgb, black, transparent 15%) 80% + ); + + // Home page - dark mode + --gray-unfilled: var(--gray-700); + // TODO: convert oklch to hex at build time + --webgl-page-background: #0f0f11; + --webgl-gray-unfilled: #413e46; + + .docs-toggle { + input { + &:checked + .docs-slider { + background: var(--pink-to-purple-horizontal-gradient) !important; + } + } + } +} + +@mixin mdc-definitions() { + --mdc-snackbar-container-shape: 0.25rem; + --mdc-snackbar-container-color: var(--page-background); + --mdc-snackbar-supporting-text-color: var(--primary-contrast); +} + +// LIGHT MODE (Explicit) +.docs-light-mode { + background-color: #ffffff; + @include root-definitions(); + @include mdc-definitions(); + .docs-invert-mode { + @include dark-mode-definitions(); + @include mdc-definitions(); + } +} + +// DARK MODE (Explicit) +.docs-dark-mode { + background-color: oklch(16.93% 0.004 285.95); + @include root-definitions(); + @include dark-mode-definitions(); + @include mdc-definitions(); + .docs-invert-mode { + @include root-definitions(); + @include mdc-definitions(); + } +} diff --git a/adev/shared-docs/styles/_faceted-list.scss b/adev/shared-docs/styles/_faceted-list.scss new file mode 100644 index 000000000000..8d1681d1e9af --- /dev/null +++ b/adev/shared-docs/styles/_faceted-list.scss @@ -0,0 +1,56 @@ +@mixin faceted-list() { + .docs-faceted-list { + --faceted-list-border-width: 2px; + list-style: none; + padding: 0; + margin: 0; + border-inline-start: calc(var(--faceted-list-border-width) - 1px) solid var(--senary-contrast); + } + .docs-faceted-list-item { + a, + button:not(.docs-expanded-button) { + position: relative; + background-color: var(--quaternary-contrast); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + transition: background-color 0.3s ease; + line-height: 1.1rem; + + &::before { + content: ''; + position: absolute; + top: 0; + left: calc(var(--faceted-list-border-width) * -1); + width: var(--faceted-list-border-width); + height: 100%; + background: var(--primary-contrast); + opacity: 0; + transform: scaleY(0.7); + transition: transform 0.3s ease, opacity 0.3s ease; + } + &:hover { + background-color: var(--primary-contrast); + &::before { + opacity: 0.3; + } + } + &.docs-faceted-list-item-active { + // font gradient + background-image: var(--pink-to-purple-vertical-gradient); + &::before { + opacity: 1; + transform: scaleY(1); + background: var(--pink-to-purple-vertical-gradient); + } + &:hover { + &::before { + opacity: 1; + transform: scaleY(1.1); + } + } + } + } + } + // a or button +} diff --git a/adev/shared-docs/styles/_kbd.scss b/adev/shared-docs/styles/_kbd.scss new file mode 100644 index 000000000000..367b51a4a85b --- /dev/null +++ b/adev/shared-docs/styles/_kbd.scss @@ -0,0 +1,33 @@ +@mixin kbd() { + // We only target non-nested kbd elements + kbd:not(:has(kbd)) { + position: relative; + color: var(---tertiary-contrast); + border: 1px solid var(--quinary-contrast); + + box-shadow: + 0 1px 0 rgba(0, 0, 0, 0.2), + 0 0 0 2px var(--octonary-contrast) inset; + // NOTE: This line (in addition to others) prevents proper contrast checking in Lighthouse + text-shadow: 0 1px 0 var(--octonary-contrast); + border-radius: 3px; + display: inline-block; + font-family: sans-serif; + line-height: 1.5; + margin: 0 0.1em; + padding: 1px 0.4em; + min-width: 14px; + min-height: 20px; + vertical-align: middle; + text-align: center; + + @media (prefers-reduced-motion: no-preference) { + *:hover > & { + box-shadow: + 0 0.5px 0 rgba(0, 0, 0, 0.2), + 0 0 0 2px var(--octonary-contrast) inset; + top: 1px; + } + } + } +} diff --git a/adev/shared-docs/styles/_links.scss b/adev/shared-docs/styles/_links.scss new file mode 100644 index 000000000000..7c463eaf8221 --- /dev/null +++ b/adev/shared-docs/styles/_links.scss @@ -0,0 +1,9 @@ +@mixin external-link-with-icon() { + &::after { + display: inline-block; + content: '\e89e'; // codepoint for "open_in_new" + font-family: 'Material Symbols Outlined'; + margin-left: 0.2rem; + vertical-align: middle; + } +} diff --git a/adev/shared-docs/styles/_media-queries.scss b/adev/shared-docs/styles/_media-queries.scss new file mode 100644 index 000000000000..8416fb2c8dcc --- /dev/null +++ b/adev/shared-docs/styles/_media-queries.scss @@ -0,0 +1,64 @@ +$screen-xs: 700px; +$screen-sm: 775px; +$screen-md: 900px; +$screen-lg: 1200px; +$screen-xl: 1800px; + +@mixin for-phone-only { + @media (max-width: $screen-xs) { + @content; + } +} +@mixin for-tablet-portrait-up { + @media (min-width: $screen-xs) { + @content; + } +} + +@mixin for-tablet { + @media (min-width: $screen-xs) and (max-width: $screen-md) { + @content; + } +} + +@mixin for-tablet-up { + @media (min-width: $screen-sm) { + @content; + } +} + +@mixin for-tablet-landscape-up { + @media (min-width: $screen-md) { + @content; + } +} + +@mixin for-desktop-up { + @media (min-width: $screen-lg) { + @content; + } +} + +@mixin for-big-desktop-up { + @media (min-width: $screen-xl) { + @content; + } +} + +@mixin for-desktop-down { + @media (max-width: $screen-lg) { + @content; + } +} + +@mixin for-tablet-landscape-down { + @media (max-width: $screen-md) { + @content; + } +} + +@mixin for-tablet-down { + @media (max-width: $screen-sm) { + @content; + } +} diff --git a/adev/shared-docs/styles/_resets.scss b/adev/shared-docs/styles/_resets.scss new file mode 100644 index 000000000000..f4e368bffea6 --- /dev/null +++ b/adev/shared-docs/styles/_resets.scss @@ -0,0 +1,377 @@ +@use './media-queries' as mq; + +@mixin resets() { + :root { + --fallback-font-stack: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --page-width: 80ch; + --layout-padding: 3.12rem; // a common padding value throughout the layout + --primary-nav-width: 110px; + --secondary-nav-width: 16.25rem; + --fixed-content-height: calc(100vh - var(--layout-padding) * 2); + + @include mq.for-tablet-landscape-down { + --layout-padding: 2rem; + } + @include mq.for-phone-only { + --layout-padding: 1rem; + } + } + + html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + // Define the default font for the document + font-family: var(--inter-font); + font-size: 16px; + background-color: var(--page-background); + color: var(--primary-contrast); + transition: + color 0.3s ease, + background-color 0.3s ease; + scroll-behavior: smooth; + } + + @media (prefers-reduced-motion) { + html { + scroll-behavior: auto; + } + } + + body { + margin: 0; + overflow-y: auto; + overflow-x: hidden; + } + + html, + body { + // Ensures that these elements extend to the full height of the viewport + height: 100vh; + min-height: 100vh; + + @supports (height: 100svh) { + height: 100svh; + } + } + + button { + cursor: pointer; + } + + img { + width: 100%; + border-radius: 0.25rem; + overflow: hidden; + margin: 1rem 0; + + &[src$='#small'] { + max-width: 250px; + } + + &[src$='#medium'] { + max-width: 450px; + } + } + + abbr[title] { + text-decoration: none; + } + + // Select & Input + .docs-form-element { + display: flex; + align-items: center; + gap: 0.5rem; + border: 1px solid var(--senary-contrast); + border-radius: 0.25rem; + padding: 0.5rem; + background-color: var(--page-background); + transition: + color 0.3s ease, + background-color 0.3s ease, + border-color 0.3s ease; + + docs-icon, + label { + color: var(--quinary-contrast); + transition: color 0.3s ease; + } + + label { + font-size: 0.875rem; + } + + select, + input { + width: 16rem; + -webkit-appearance: none; + display: flex; + flex: 1; + font-size: 0.875rem; + border: none; + outline: none; + height: 100%; + background-color: var(--page-background); + color: var(--tertiary-contrast); + transition: + color 0.3s ease, + background-color 0.3s ease; + } + + select { + width: 10rem; + background-image: url('../icons/chevron.svg'); + background-size: 0.7rem; + background-repeat: no-repeat; + background-position: right center; + margin-inline-end: 0.3rem; + } + + &:focus-within { + border: 1px solid var(--french-violet); + docs-icon { + color: var(--tertiary-contrast); + } + } + } + + // Progress bar styling + .ng-spinner { + display: none !important; + } + + .ng-progress-bar { + .ng-bar { + background: var(--red-to-pink-to-purple-horizontal-gradient) !important; + } + } + + // Material tab styling + .mat-mdc-tab-header { + --mat-tab-header-disabled-ripple-color: transparent; + --mat-tab-header-pagination-icon-color: var(--secondary-contrast); + --mat-tab-header-inactive-label-text-color: var(--secondary-contrast); + --mat-tab-header-inactive-ripple-color: transparent; + --mat-tab-header-inactive-hover-label-text-color: var(--tertiary-contrast); + --mat-tab-header-inactive-focus-label-text-color: var(--secondary-contrast); + --mat-tab-header-active-label-text-color: var(--primary-contrast); + --mat-tab-header-active-ripple-color: transparent; + --mdc-tab-indicator-active-indicator-color: color-mix(in srgb, var(--bright-blue) 40%, white); + --mat-tab-header-active-focus-label-text-color: var(--primary-contrast); + --mat-tab-header-active-hover-label-text-color: var(--primary-contrast); + --mat-tab-header-active-focus-indicator-color: var(--bright-blue); + --mat-tab-header-active-hover-indicator-color: color-mix( + in srgb, + var(--bright-blue) 40%, + white + ); + + .mdc-tab { + --mat-tab-header-label-text-font: Inter, sans-serif; + --mat-tab-header-label-text-letter-spacing: -0.00875rem; + --mat-tab-header-label-text-size: 0.875rem; + --mat-tab-header-label-text-weight: 500; + } + + .mdc-tab__text-label { + user-select: none; + letter-spacing: -0.00875rem; + } + + .mdc-tab--active { + --mat-tab-header-label-text-weight: 500; + } + } + + // Material tab styling on Reference page + .docs-reference-tabs { + .mat-mdc-tab-labels { + gap: 20px; + border-bottom: 1px solid var(--senary-contrast); + transition: border-color 0.3s ease; + } + .mdc-tab__text-label { + letter-spacing: -0.00875rem; + } + + .mdc-tab { + min-width: min-content !important; + padding-inline: 2px !important; + } + } + + // Tabs on Tutorials page, Tutorials Playground + .docs-tutorial-editor, + .docs-code-editor-tabs, + .docs-editor-tabs { + .mat-mdc-tab-header { + --mdc-tab-indicator-active-indicator-color: transparent; + --mat-tab-header-active-focus-indicator-color: transparent; + --mat-tab-header-active-hover-indicator-color: transparent; + --mat-tab-header-label-text-font: InterTight, sans-serif; + --mat-tab-header-label-text-tracking: -0.00875rem; + --mat-tab-header-label-text-size: 0.8125rem; + + .mat-mdc-tab-labels { + gap: 0; + transition: border-color 0.3s ease; + width: 100%; + } + + .mdc-tab { + padding-inline: 18px !important; + } + + .mdc-tab--active { + border: 0; + border-block-end-width: 2px; + border-style: solid; + border-image: var(--pink-to-purple-horizontal-gradient) 1; + + &.cdk-keyboard-focused { + border-image: var(--blue-to-cyan-vertical-gradient) 1; + } + + &:has(.docs-delete-file) { + padding-inline-start: 18px !important; + padding-inline-end: 0.5px !important; + } + } + } + } + + .docs-editor-tabs { + .mat-mdc-tab-header { + border-block-end: 1px solid var(--senary-contrast); + } + } + + .docs-code-editor-tabs { + .mat-mdc-tab-group { + // adjust width for + (add file) button + max-width: calc(100% - 40px); + } + } + + .docs-editor-tabs, + .docs-code-editor-tabs { + .mat-mdc-tab-header { + background-color: var(--octonary-contrast); + transition: + background-color 0.3s ease, + border-color 0.3s ease; + } + + .mdc-tab__text-label { + i { + color: var(--bright-blue); + margin-inline-start: 0.5rem; + margin-inline-end: 0.25rem; + font-size: 1.25rem; + } + span { + color: var(--primary-contrast); + } + } + } + + // Tabs inside Example Viewer Header + .docs-example-viewer-actions { + .mat-mdc-tab-labels { + width: 100%; + } + + .mat-mdc-tab-header { + --mdc-tab-indicator-active-indicator-color: transparent; + --mat-tab-header-active-focus-indicator-color: transparent; + --mat-tab-header-active-hover-indicator-color: transparent; + --mat-tab-header-active-focus-label-text-color: transparent; + --mat-tab-header-active-hover-label-text-color: transparent; + --mat-tab-header-active-label-text-color: transparent; + --mat-tab-header-label-text-font: InterTight, sans-serif; + --mat-tab-header-label-text-letter-spacing: -0.00875rem; + --mat-tab-header-label-text-size: 0.8125rem; + + .mat-mdc-tab-labels { + gap: 0; + border-bottom: 0; + } + + .mdc-tab { + padding-inline: 15px !important; + } + + .mdc-tab--active { + border: 0; + border-block-end-width: 2px; + border-style: solid; + border-image: var(--purple-to-blue-horizontal-gradient) 1; + + span { + background-image: var(--purple-to-blue-horizontal-gradient); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + } + } + } + } +} + +.cm-editor, +.ͼ3 .cm-gutters, +.cm-scroller { + background-color: var(--page-background); + transition: background-color 0.3s ease; + font-size: 0.875rem; +} + +.ͼ1.cm-focused { + outline: none; +} + +.ͼ2u { + .cm-line.cm-activeLine, + .cm-activeLineGutter { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); + transition: background-color 0.3s ease; + } +} + +.ͼ1 .cm-button { + background-image: linear-gradient(var(--octonary-contrast), var(--page-background)); + + &:focus { + background-image: linear-gradient(var(--senary-contrast), var(--page-background)); + } +} + +.cm-scroller { + &::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); + cursor: pointer; + margin: 2px; + } + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--senary-contrast); + border-radius: 10px; + transition: background-color 0.3s ease; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--quaternary-contrast); + } +} + +[docs-breadcrumb] { + height: 2.5625rem; +} diff --git a/adev/shared-docs/styles/_scroll-track.scss b/adev/shared-docs/styles/_scroll-track.scss new file mode 100644 index 000000000000..3650d12a7f21 --- /dev/null +++ b/adev/shared-docs/styles/_scroll-track.scss @@ -0,0 +1,78 @@ +@mixin scroll-track { + // used on secondary nav + .docs-scroll-hide { + &::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); + } + &::-webkit-scrollbar { + width: 0; + } + } + + // used for main page scroll + .docs-scroll-track-transparent-large { + &::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); + cursor: pointer; + } + + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--quinary-contrast); + border-radius: 10px; + transition: background-color 0.3s ease; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--quaternary-contrast); + } + } + + // used on table & secondary navigation + .docs-scroll-track-transparent { + &::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); + cursor: pointer; + } + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--senary-contrast); + border-radius: 10px; + transition: background-color 0.3s ease; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--quaternary-contrast); + } + } + + // used on docs-code blocks + .docs-mini-scroll-track { + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--senary-contrast); + border-radius: 10px; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--quinary-contrast); + } + } +} diff --git a/adev/shared-docs/styles/_split.scss b/adev/shared-docs/styles/_split.scss new file mode 100644 index 000000000000..635d93fcce78 --- /dev/null +++ b/adev/shared-docs/styles/_split.scss @@ -0,0 +1,27 @@ +$gutter-border: 1px solid var(--senary-contrast) !important; + +as-split { + ::ng-deep .as-split-gutter { + flex-basis: 5px !important; + background-color: inherit !important; + position: relative; + } + + &.as-horizontal.docs-editor { + ::ng-deep .as-split-gutter { + border-inline: $gutter-border; + } + } + + &.as-vertical.docs-editor { + ::ng-deep .as-split-gutter { + border-block-start: $gutter-border; + } + } + + &.as-vertical.docs-right-side { + ::ng-deep .as-split-gutter { + border-block-start: $gutter-border; + } + } +} diff --git a/adev/shared-docs/styles/_typography.scss b/adev/shared-docs/styles/_typography.scss new file mode 100644 index 000000000000..a8492e28f891 --- /dev/null +++ b/adev/shared-docs/styles/_typography.scss @@ -0,0 +1,110 @@ +// Base Typography styles + +@mixin typography() { + :root { + --code-font: 'DM Mono', monospace; + --inter-font: 'Inter', var(--fallback-font-stack); + --inter-tight-font: 'Inter Tight', var(--fallback-font-stack); + --icons: 'Material Symbols Outlined'; + + --selection-background: var(--vivid-pink); + --selection-color: var(--vivid-pink); + } + + :nth-child(6n + 1) { + --selection-color: var(--vivid-pink); + } + :nth-child(6n + 2) { + --selection-background: var(--hot-pink); + --selection-color: var(--hot-pink); + } + :nth-child(6n + 3) { + --selection-background: var(--electric-violet); + --selection-color: var(--electric-violet); + } + :nth-child(6n + 4) { + --selection-background: var(--french-violet); + --selection-color: var(--french-violet); + } + :nth-child(6n + 5) { + --selection-background: var(--indigo-blue); + --selection-color: var(--indigo-blue); + } + :nth-child(6n + 6) { + --selection-background: var(--bright-blue); + --selection-color: var(--bright-blue); + } + + ::selection { + // Added fallback color due to browser idiosyncrasies with color-mix and ::selection + background: color-mix(in srgb, var(--selection-background) 10%, var(--octonary-contrast)); + color: color-mix(in srgb, var(--selection-color) 40%, var(--primary-contrast)); + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-family: var(--inter-tight-font); + font-weight: 500; + text-wrap: balance; + } + + p { + font-size: 0.875rem; + line-height: 1.4rem; + font-weight: 400; + letter-spacing: -0.00875rem; + } + + p ~ ul, + p ~ ol { + margin-block-start: 0; + } + + ul, + ol { + font-size: 0.875rem; + line-height: 1.4rem; + font-weight: 400; + letter-spacing: -0.01rem; + } + + a { + text-decoration: none; + font-weight: 500; + transition: color 0.3s ease; + } + + p > a, + td > a, + div > a:not(.docs-card), + li:not(.docs-faceted-list *) a { + color: var(--bright-blue); + &:hover { + color: var(--vivid-pink); + } + &:active { + color: var(--hot-red); + } + } + + p > a, + .docs-list a, + .docs-card a { + margin-block: 0; + text-decoration: underline; + } + + hr { + border: 0; + border-block-start-width: 1px; + border-style: solid; + border-color: var(--senary-contrast); + width: 100%; + margin-block: 1rem; + transition: border-color 0.3s ease; + } +} diff --git a/adev/shared-docs/styles/_z-index.scss b/adev/shared-docs/styles/_z-index.scss new file mode 100644 index 000000000000..8d7bb4566385 --- /dev/null +++ b/adev/shared-docs/styles/_z-index.scss @@ -0,0 +1,7 @@ +:root { + --z-index-mini-menu: 200; + --z-index-nav: 100; + --z-index-cookie-consent: 60; + --z-index-content: 50; + --z-index-icon: 10; +} diff --git a/adev/shared-docs/styles/docs/_alert.scss b/adev/shared-docs/styles/docs/_alert.scss new file mode 100644 index 000000000000..0bc4f6faffbf --- /dev/null +++ b/adev/shared-docs/styles/docs/_alert.scss @@ -0,0 +1,117 @@ +// Alert + +@mixin docs-alert() { + .docs-alert { + // Default theme is purple to blue + --alert-gradient: var(--purple-to-blue-vertical-gradient); + --alert-accent: var(--bright-blue); + border-width: 0; + border-inline-start-width: 3px; + border-style: solid; + background: color-mix(in srgb, var(--alert-accent) 5%, transparent); + color: var(--primary-contrast); + border-image: var(--alert-gradient) 1; + padding: 1.5rem; + font-weight: 400; + transition: color 0.3s ease; + margin-block: 1rem; + position: relative; + + &::before { + font-family: var(--icons); + // content: icon is defined in each docs-alert class below... + position: absolute; + margin-top: -0.05rem; + color: var(--alert-accent); + font-size: 1.3rem; + } + + p { + margin-inline-start: 1.65rem; + } + + .docs-dark-mode & { + background: color-mix(in srgb, var(--alert-accent) 10%, transparent); + } + + .docs-pill-row { + margin-block-end: 0; + } + } + + .docs-viewer .docs-alert p { + margin-block: 0; + } + + .docs-alert-note { + --alert-gradient: var(--blue-to-teal-vertical-gradient); + --alert-accent: var(--bright-blue); + &::before { + content: 'bookmark'; + } + } + + .docs-alert-tip { + --alert-gradient: var(--green-to-cyan-vertical-gradient); + --alert-accent: var(--symbolic-cyan); + &::before { + content: 'star'; + } + } + + .docs-alert-todo { + --alert-gradient: var(--black-to-gray-vertical-gradient); + --alert-accent: var(--quaternary-contrast); + &::before { + content: 'error'; + } + } + + .docs-alert-question { + --alert-gradient: var(--blue-to-cyan-vertical-gradient); + --alert-accent: var(--symbolic-cyan); + &::before { + content: 'help'; + } + } + + .docs-alert-summary { + --alert-gradient: var(--purple-to-light-purple-vertical-gradient); + --alert-accent: var(--electric-violet); + &::before { + content: 'sms'; + } + } + + .docs-alert-tldr { + --alert-gradient: var(--pink-to-purple-vertical-gradient); + --alert-accent: var(--vivid-pink); + &::before { + content: 'speaker_notes'; + } + } + + .docs-alert-critical { + --alert-gradient: var(--red-to-orange-vertical-gradient); + --alert-accent: var(--orange-red); + &::before { + content: 'warning'; + } + } + + .docs-alert-important { + --alert-gradient: var(--red-to-pink-vertical-gradient); + --alert-accent: var(--hot-red); + &::before { + content: 'priority_high'; + } + } + + .docs-alert-helpful { + --alert-gradient: var(--orange-to-pink-vertical-gradient); + --alert-accent: var(--vivid-pink); + &::before { + content: 'check_circle'; + } + } +} diff --git a/adev/shared-docs/styles/docs/_callout.scss b/adev/shared-docs/styles/docs/_callout.scss new file mode 100644 index 000000000000..91a8e46ba7ea --- /dev/null +++ b/adev/shared-docs/styles/docs/_callout.scss @@ -0,0 +1,72 @@ +// Callout + +@mixin docs-callout() { + .docs-callout { + // Default theme is purple to blue + --callout-theme: var(--purple-to-blue-horizontal-gradient); + border-width: 0; + border-block-start-width: 2px; + border-block-end-width: 1px; + border-style: solid; + margin-block: 1.5rem; + border-image: var(--callout-theme) 1; + position: relative; + + // Removes bottom line if followed by another callout + // Prevents too many lines/visual noise + &:has(+ .docs-callout) { + border-block-end-width: 0; + } + + &::before { + font-family: var(--icons); + // content: icon is defined in each docs-alert class below... + position: absolute; + right: 0; + margin-top: 1.35rem; + color: var(--alert-accent); + font-size: 1.3rem; + } + + // Callout heading + h2, + h3, + h4, + h5, + h6 { + background-image: var(--callout-theme); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + max-width: fit-content; + } + } + .docs-viewer .docs-callout h3 { + font-size: 0.875rem; + margin-block: 1.6rem; + } + + .docs-callout-helpful { + --callout-theme: var(--purple-to-blue-horizontal-gradient); + &::before { + content: 'check_circle'; + color: var(--bright-blue); + } + } + + .docs-callout-critical { + --callout-theme: var(--red-to-orange-horizontal-gradient); + &::before { + content: 'warning'; + color: var(--orange-red); + } + } + + .docs-callout-important { + --callout-theme: var(--pink-to-purple-horizontal-gradient); + &::before { + content: 'priority_high'; + color: var(--electric-violet); + } + } +} diff --git a/adev/shared-docs/styles/docs/_card.scss b/adev/shared-docs/styles/docs/_card.scss new file mode 100644 index 000000000000..e50c1ff65457 --- /dev/null +++ b/adev/shared-docs/styles/docs/_card.scss @@ -0,0 +1,100 @@ +// Card Grid + +@mixin docs-card() { + .docs-card-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + @container docs-content (max-width: 450px) { + grid-template-columns: 1fr; + } + grid-gap: 1.25rem; + margin-block: 1rem; + } + + .docs-card { + display: flex; + flex-direction: column; + justify-content: space-between; + color: var(--primary-contrast); + padding: 1.5rem; + border: 1px solid var(--senary-contrast); + border-radius: 0.25rem; + overflow: hidden; + transition: border-color 0.3s ease, background-color 0.3s ease; + + p:first-of-type { + margin-block-start: 1.5rem; + } + + p:last-of-type { + margin-block-end: 1.5rem; + } + + span { + font-size: 0.875rem; + font-weight: 500; + margin-block: 0; + + position: relative; + } + + * + *:not(a):not(code):not(span) { + margin-block: 1.5rem; + } + + &.docs-card-with-svg { + padding: 0; + + .docs-card-text-content { + flex-grow: 1; + margin-block-start: 0; + padding-inline: 1.5rem; + display: flex; + flex-direction: column; + justify-content: space-between; + border-block-start: 1px solid var(--senary-contrast); + h3 { + margin-bottom: 0; + margin-block-start: 1rem; + font-size: 1rem; + } + p { + margin-block-start: 0; + } + } + } + } + + // docs-card with link + a.docs-card { + display: flex; + flex-direction: column; + justify-content: space-between; + + span { + background: var(--pink-to-highlight-to-purple-to-blue-horizontal-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + font-size: 0.875rem; + margin-block: 0; + + transition: background-position 1.8s ease-out; + background-size: 200% 100%; + background-position: 100% 0%; + position: relative; + } + + &:hover { + span { + background-position: 0% 0%; + } + background: var(--subtle-purple); + } + } + + .docs-viewer .docs-card h3 { + margin-block-start: 0; + font-size: 1rem; + } +} diff --git a/adev/shared-docs/styles/docs/_code.scss b/adev/shared-docs/styles/docs/_code.scss new file mode 100644 index 000000000000..4257fcf31a5e --- /dev/null +++ b/adev/shared-docs/styles/docs/_code.scss @@ -0,0 +1,377 @@ +// TODO: Working on refactoring all code components & syntax highlighting + +/* stylelint-disable */ + +@mixin docs-code-block { + // code across docs, inline, blocks, shell, example viewer, etc. + code { + font-family: var(--code-font); + border-radius: 0.25rem; + font-weight: 400; + // Create a new stacking context to allow for the psuedo element content to be placed behind the + // text so that the text is properly selectable by the user. + isolation: isolate; + + // Inline code only + &:not(pre *) { + position: relative; + padding: 0 0.3rem; + // Fallback for older browsers + background: #e62600; + background: var(--red-to-orange-horizontal-gradient); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + max-width: max-content; + width: 100%; + display: inline-block; + + &::before { + content: ''; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + background: var(--subtle-purple); + border-radius: 0.25rem; + z-index: -1; + } + + a:not(.docs-anchor) > & { + position: relative; + padding: 0 0.3rem; + white-space: nowrap; + background: var(--purple-to-blue-horizontal-gradient); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + max-width: max-content; + + &::before { + content: ''; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + background: var(--subtle-purple); + border-radius: 0.25rem; + transition: background 0.3s ease; + z-index: -1; + } + + &:hover { + background: var(--vivid-pink); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + max-width: max-content; + } + } + } + + // render inline code emjois without gradient + .docs-emoji { + color: initial; + } + } + + pre { + white-space: pre; + } + + // render inline code emjois without gradient + .docs-emoji { + color: inherit; + } + + // docs-code shell, multifile, mermaid diagrams + .docs-code { + .docs-viewer & { + margin-block: 1rem; + } + display: block; + position: relative; + border: 1px solid var(--senary-contrast); + border-radius: 0.25rem; + background: var(--octonary-contrast); + transition: + background 0.3s ease, + border-color 0.3s ease; + container: codeblock / inline-size; + + pre { + overflow-x: auto; + } + + code { + display: flex; + flex-direction: column; + font-size: 0.875rem; + counter-reset: line; + } + } + + // shell doesn't have a header, for commands only + .shell { + border: 1px solid var(--quinary-contrast); + + pre { + white-space: nowrap; + } + + .shiki .line { + &::before { + content: '$'; + padding-inline-end: 0.5rem; + } + display: block; + + &.hidden { + display: none; + } + } + + button[docs-copy-source-code] { + background-color: var(--quaternary-contrast); + border: 1px solid var(--quinary-contrast); + + @container codeblock (min-width: 400px) { + border: 1px solid transparent; + } + + .docs-copy { + path { + fill: var(--quinary-contrast); + } + } + .docs-check { + color: var(--page-background); + } + &:hover { + .docs-copy { + path { + fill: var(--octonary-contrast); + } + } + } + } + } + + // copy code button + button[docs-copy-source-code] { + padding: 0.375rem 0.4rem 0.15rem 0.5rem; + position: absolute; + top: 3.1rem; + right: 0.2rem; + border-radius: 0.25rem; + cursor: pointer; + z-index: var(--z-index-icon); + background-color: var(--octonary-contrast); + border: 1px solid var(--senary-contrast); + transition: + background-color 0.3s ease, + border-color 0.3s ease; + + @container codeblock (min-width: 400px) { + border: 1px solid transparent; + } + + .docs-icon { + transition: opacity 0.3s ease-out; + } + .docs-copy { + opacity: 1; + path { + transition: fill 0.3s ease; + fill: var(--gray-400); + } + } + .docs-check { + opacity: 0; + color: var(--primary-contrast); + position: absolute; + inset: 0; + top: 0.35rem; + path { + transition: fill 0.3s ease; + } + } + + &.docs-copy-source-code-button-success { + .docs-copy { + opacity: 0; + } + .docs-check { + opacity: 1; + } + } + + &:hover { + .docs-copy { + path { + fill: var(--primary-contrast); + } + } + } + } + + .docs-code .docs-code-header { + position: relative; + h3 { + background-image: var(--purple-to-blue-horizontal-gradient); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + font-size: 0.875rem; + font-style: normal; + font-weight: 400; + line-height: 1.4rem; + letter-spacing: -0.00875rem; + margin: 0; + word-wrap: break-word; + width: fit-content; + } + } + + .docs-code-header { + padding: 0.75rem; + + // docs header background + &::before { + content: ''; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + background: var(--subtle-purple); + border-radius: 0.25rem 0.25rem 0 0; + transition: background 0.3s ease; + } + } + + // Single line docs-code elements, without headers, shell code + .docs-code:not(:has(.docs-code-header)) { + button[docs-copy-source-code] { + top: 0.2rem; + right: 0.2rem; + } + } + + .docs-code[mermaid] { + border: 0; + width: 100%; + background-color: transparent; + } + + // Line numbers styling: Add a grid, if there are line numbers + .docs-code:not([mermaid]), + .docs-example-viewer-code-wrapper { + code:has(.shiki-ln-number) { + display: grid; + grid-template-columns: min-content 1fr; + height: 100%; + } + + pre { + overflow-x: auto; + display: flex; + flex-direction: column; + } + } + + .docs-example-viewer-code-wrapper { + .docs-code-header { + display: none; + } + } + + .shiki-ln-number { + border-inline-end: 1px solid var(--senary-contrast); + padding-inline-start: 0.75rem; + padding-inline-end: 0.5rem; + color: var(--quaternary-contrast); + font-size: 0.875rem; + text-align: right; + } + + .highlighted { + background: color-mix(in srgb, var(--bright-blue) 9%, var(--page-background)); + color: color-mix(in srgb, var(--bright-blue) 60%, var(--full-contrast)); + } + .remove { + background: color-mix(in srgb, var(--orange-red) 10%, var(--page-background)); + color: color-mix(in srgb, var(--hot-red) 80%, var(--full-contrast)); + } + .add { + background: color-mix(in srgb, oklch(68.82% 0.224 155.13) 12%, var(--page-background)); + color: color-mix(in srgb, var(--super-green), var(--full-contrast) 50%); + } + .hidden { + display: none; + } +} + +@mixin docs-syntax-highlighting { + .shiki .line { + min-height: 1.375em; + padding-inline: 1rem; + color: var(--tertiary-contrast); + + display: block; + &.hidden { + display: none; + } + + span:last-child { + margin-inline-end: 4rem; + } + } + + .shiki-ln-group { + display: flex; + flex-direction: column; + margin: 1rem 0; + } + + .shiki-deprecated { + text-decoration: line-through; + } + + .gap { + color: var(--quaternary-contrast); + } + + .hljs-constructor { + color: var(--symbolic-cyan); + } + + .hljs-params { + color: var(--bright-blue); + } +} + +@mixin docs-code-editor { + .cm-tooltip-hover { + display: flex; + flex-direction: column-reverse; + padding: 0.75rem; + + &::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0); + } + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--senary-contrast); + border-radius: 10px; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--quinary-contrast); + } + } +} diff --git a/adev/shared-docs/styles/docs/_decorative-header.scss b/adev/shared-docs/styles/docs/_decorative-header.scss new file mode 100644 index 000000000000..b26f982a81a6 --- /dev/null +++ b/adev/shared-docs/styles/docs/_decorative-header.scss @@ -0,0 +1,100 @@ +@mixin docs-decorative-header() { + .docs-decorative-header-container { + container: header / inline-size; + } + + .docs-decorative-header { + border-radius: 0.625rem; + background: var(--septenary-contrast); + max-width: var(--page-width); + overflow: hidden; + display: flex; + position: relative; + transition: background 0.3s ease; + + @container header (max-width: 550px) { + flex-direction: column-reverse; + } + + .docs-header-content { + box-sizing: border-box; + padding: 1.5rem; + padding-inline-end: 0; + flex-grow: 1; + + @container header (max-width: 550px) { + width: 100%; + padding-block-end: 1.5rem; + } + + h1, + p, + span { + color: var(--primary-contrast); + transition: color 0.3s ease; + } + + a { + position: absolute; + top: 1.5rem; + right: 1.5rem; + z-index: 20; + i { + color: var(--quaternary-contrast); + } + + &:hover { + i { + color: var(--primary-contrast); + } + } + } + + docs-breadcrumb { + padding-block-end: 1rem; + } + + .docs-breadcrumb { + font-size: 0.875rem; + span { + color: var(--primary-contrast) !important; + } + } + } + + svg { + margin: 0; + margin-block: auto; + padding-inline: 1rem 3.5rem; + padding-block: 2rem; + min-width: 150px; + max-width: 250px; + max-height: 125px; + z-index: 0; + + &.docs-what-is-angular-svg { + max-height: 125px; + min-width: 175px; + padding-block-end: 0.5rem; + } + + &.docs-directives-svg { + max-height: 150px; + } + + &.docs-roadmap-svg { + padding-block-end: 0.5rem; + } + + @container header (max-width: 550px) { + padding: 2rem; + padding-block-end: 0; + padding-inline-start: 1.5rem; + width: fit-content; + min-width: auto; + max-width: 80%; + max-height: 125px; + } + } + } +} diff --git a/adev/shared-docs/styles/docs/_icon.scss b/adev/shared-docs/styles/docs/_icon.scss new file mode 100644 index 000000000000..06477d299db7 --- /dev/null +++ b/adev/shared-docs/styles/docs/_icon.scss @@ -0,0 +1,13 @@ +// Icon styles, primarily for docs + +@mixin docs-icon { + .docs-icon { + color: var(--quinary-contrast); + font-size: 1.5rem; + transition: color 0.3s ease; + } + + .docs-icon-small { + font-size: 1rem; + } +} diff --git a/adev/shared-docs/styles/docs/_mermaid.scss b/adev/shared-docs/styles/docs/_mermaid.scss new file mode 100644 index 000000000000..4181763e405e --- /dev/null +++ b/adev/shared-docs/styles/docs/_mermaid.scss @@ -0,0 +1,65 @@ +#mermaid-generated-diagram { + --fontFamily: 'sans-serif'; + --primaryColor: '#fff'; + --primaryBorderColor: '#000'; + --pie1: '#0546ff'; + --pie2: '#f637e3'; + --pie3: '#f11653'; + --pie4: '#8001c6'; + --pie5: '#00c572'; + --pie6: '#fe3700'; + + background-color: var(--page-background) !important; // svg background color + g { + rect { + stroke: black !important; // border around the rectangles, same for dark/light theme + filter: drop-shadow(5px 5px 0px var(--vivid-pink)); + } + } + .messageText, + .pieTitleText { + fill: var(--primary-contrast) !important; // pie chart title text and line labels + } + .pieOuterCircle { + stroke-width: 1px; + } + .pieCircle { + stroke-width: 1.5px; + } + .legend { + rect { + filter: none; + opacity: 0.7; + } + text { + fill: var(--primary-contrast) !important; // legend label text color + } + } + .slice { + // e.g. text on the pie charts + fill: var(--primary-contrast) !important; + } + .flowchart-link, + line { + // lines + stroke: var(--primary-contrast) !important; + } + .marker, + #statediagram-barbEnd, + .transition, + #arrowhead path { + // arrows + stroke: var(--primary-contrast) !important; + fill: var(--primary-contrast) !important; + } + .cluster rect, + .nodes rect { + stroke: var(--primary-contrast) !important; + fill: var(--page-background) !important; + } + + .nodeLabel { + fill: var(--primary-contrast) !important; + color: var(--primary-contrast) !important; + } +} diff --git a/adev/shared-docs/styles/docs/_pill.scss b/adev/shared-docs/styles/docs/_pill.scss new file mode 100644 index 000000000000..60dd1f49c0ad --- /dev/null +++ b/adev/shared-docs/styles/docs/_pill.scss @@ -0,0 +1,72 @@ +// Pill + +@mixin docs-pill() { + .docs-pill { + display: flex; + align-items: center; + // Default blue + --pill-accent: var(--bright-blue); + background: color-mix(in srgb, var(--pill-accent) 5%, transparent); + // Darken the text a bit for contrast + color: color-mix(in srgb, var(--pill-accent) 70%, var(--full-contrast)); + padding-inline: 0.75rem; + padding-block: 0.375rem; + border-radius: 2.75rem; + border: 0; + transition: background 0.3s ease; + + font-family: var(--inter-font); + font-size: 0.875rem; + font-style: normal; + font-weight: 500; + line-height: 1.4rem; + letter-spacing: -0.00875rem; + + &:hover { + background: color-mix(in srgb, var(--pill-accent) 15%, transparent); + } + + .docs-icon-small { + margin-inline-start: 0.25rem; + } + + .docs-dark-mode & { + // Lighten the text a bit for contrast + color: color-mix(in srgb, var(--pill-accent) 60%, white 70%); + background: color-mix(in srgb, var(--pill-accent) 10%, white 2%); + &:hover { + background: color-mix(in srgb, var(--pill-accent) 20%, white 10%); + } + } + } + + .docs-pill-row { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5rem; + margin-block: 0.75rem; + .docs-pill { + // TODO: This gradient supports longer rows + // we may want to refine it + &:nth-child(6n + 1) { + --pill-accent: var(--hot-red); + } + &:nth-child(6n + 2) { + --pill-accent: var(--hot-pink); + } + &:nth-child(6n + 3) { + --pill-accent: var(--electric-violet); + } + &:nth-child(6n + 4) { + --pill-accent: var(--french-violet); + } + &:nth-child(6n + 5) { + --pill-accent: var(--indigo-blue); + } + &:nth-child(6n + 6) { + --pill-accent: var(--bright-blue); + } + } + } +} diff --git a/adev/shared-docs/styles/docs/_steps.scss b/adev/shared-docs/styles/docs/_steps.scss new file mode 100644 index 000000000000..a074fd92d716 --- /dev/null +++ b/adev/shared-docs/styles/docs/_steps.scss @@ -0,0 +1,81 @@ +// Doc Steps/Ordered Doc section +// Did somebody order a doc? +@use '../media-queries' as mq; + +@mixin docs-steps() { + .docs-steps { + --gutter: 4rem; + padding-inline-start: var(--gutter); + counter-reset: code-steps-list; + list-style-type: none; + li { + position: relative; + } + } + .docs-steps li h3 { + font-size: 1.75rem; + margin-block-start: 0; + margin-block-end: 0.5rem; + line-height: 2.5rem; + } + .docs-step-number { + counter-increment: code-steps-list; + display: block; + pointer-events: none; + position: absolute; + left: calc(var(--gutter) * -1); + top: 2.7rem; + bottom: 0; + &::before { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + content: counter(code-steps-list); + border-radius: 50%; + aspect-ratio: 1 / 1; + border: 1px solid transparent; + background-image: linear-gradient(var(--page-background), var(--page-background)), + var(--pink-to-purple-horizontal-gradient); + background-origin: border-box; + background-clip: content-box, border-box; + position: sticky; + top: 2rem; + // adjust for tablet nav bar height + @include mq.for-tablet-landscape-down { + top: calc(1rem + 75px); + } + // adjust for mobile nav bar height + @include mq.for-phone-only { + top: calc(1rem + 55px); + } + } + + .docs-tutorial-content & { + &::before { + // calc(1rem + sticky tutorial nav height) + top: calc(1rem + 120px); + + // adjust for tablet nav bar height + @include mq.for-tablet-landscape-down { + top: calc(1rem + 165px); + } + + // adjust for mobile nav bar height + @include mq.for-phone-only { + top: calc(1rem + 140px); + } + } + } + + .docs-tutorial-content:has(.docs-reveal-answer-button) & { + &::before { + // calc(1rem + sticky tutorial nav height + // + reveal answer button height when on smaller screens) + @container tutorial-content (max-width: 430px) { + top: calc(1rem + 175px); + } + } + } + } +} diff --git a/adev/shared-docs/styles/docs/_table.scss b/adev/shared-docs/styles/docs/_table.scss new file mode 100644 index 000000000000..ff3c033f31fe --- /dev/null +++ b/adev/shared-docs/styles/docs/_table.scss @@ -0,0 +1,43 @@ +// Table + +@mixin docs-table { + .docs-table { + overflow-x: auto; + + table { + width: 100%; + border-collapse: collapse; + margin-block: 1rem; + font-size: 0.875rem; + line-height: 160%; + letter-spacing: -0.00875rem; + } + + th { + text-align: left; + padding-block: 0.4rem; + padding-inline-end: 1.5rem; + border-block: 1px solid var(--senary-contrast); + font-size: 0.75rem; + font-weight: 600; + } + + tr { + td { + padding-block: 0.85rem; + vertical-align: top; + &:not(:last-child) { + padding-inline-end: 1rem; + } + } + td:first-child { + padding-inline-end: 1.62rem; + vertical-align: top; + min-width: 10ch; + } + &:not(:last-child) { + border-block-end: 1px solid var(--senary-contrast); + } + } + } +} diff --git a/adev/shared-docs/styles/docs/_video.scss b/adev/shared-docs/styles/docs/_video.scss new file mode 100644 index 000000000000..c20a6bb8e2b9 --- /dev/null +++ b/adev/shared-docs/styles/docs/_video.scss @@ -0,0 +1,11 @@ +@mixin docs-video() { + .docs-video-container { + iframe { + border: 0; + width: 100%; + border-radius: 0.25rem; + overflow: hidden; + aspect-ratio: 16 / 9; + } + } +} diff --git a/adev/shared-docs/styles/global-styles.scss b/adev/shared-docs/styles/global-styles.scss new file mode 100644 index 000000000000..876100d89e78 --- /dev/null +++ b/adev/shared-docs/styles/global-styles.scss @@ -0,0 +1,136 @@ +// TODO: Continue organizing and refactoring this file +@use '@angular/material' as mat; + +// Using disable-next-line to avoid stylelint errors - these imports are necessary +// TODO: Is there another way to prevent these linting errors? +// stylelint-disable-next-line @angular/no-unused-import +@use '_colors'; +// stylelint-disable-next-line @angular/no-unused-import +@use '_z-index'; + +// Global +@use 'resets'; +@use 'typography'; +@use 'scroll-track'; +@use 'button'; +@use 'kbd'; +@use 'api-item-label'; +@use 'faceted-list'; +@use 'media-queries' as mq; + +// Docs +@use 'docs/alert'; +@use 'docs/callout'; +@use 'docs/card'; +@use 'docs/code'; +@use 'docs/decorative-header'; +@use 'docs/icon'; +@use 'docs/pill'; +@use 'docs/steps'; +@use 'docs/table'; +@use 'docs/video'; +@use 'docs/mermaid'; + +// Global +@include resets.resets(); +@include typography.typography(); +@include scroll-track.scroll-track(); +@include button.button(); +@include kbd.kbd(); +@include api-item-label.api-item-label(); +@include faceted-list.faceted-list(); + +@include mq.for-phone-only(); +@include mq.for-tablet-portrait-up(); +@include mq.for-tablet-landscape-up(); +@include mq.for-desktop-up(); +@include mq.for-big-desktop-up(); +@include mq.for-tablet-landscape-down(); + +// temporary just to show different options of code component UI. +$primary: mat.m2-define-palette(mat.$m2-indigo-palette); +$accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400); +$theme: mat.m2-define-light-theme( + ( + color: ( + primary: $primary, + accent: $accent, + ), + typography: mat.m2-define-typography-config(), + ) +); + +// Include material core styles. +@include mat.core(); +@include mat.tabs-theme($theme); +@include mat.button-toggle-theme($theme); + +// Include custom docs styles +@include alert.docs-alert(); +@include callout.docs-callout(); +@include card.docs-card(); +@include code.docs-code-block(); +@include code.docs-code-editor(); +@include decorative-header.docs-decorative-header(); +@include icon.docs-icon(); +@include pill.docs-pill(); +@include steps.docs-steps(); +@include code.docs-syntax-highlighting(); +@include table.docs-table(); +@include video.docs-video(); + +// Include custom angular.dev styles + +// Disable view transitions when reduced motion is requested. +@media (prefers-reduced-motion) { + ::view-transition-group(*), + ::view-transition-old(*), + ::view-transition-new(*) { + animation: none !important; + } +} + + +.docs-dark-mode .shiki { + color: var(--shiki-dark) ; + background-color: var(--shiki-dark-bg) ; + + span { + color: var(--shiki-dark) ; + background-color: var(--shiki-dark-bg) ; + /* Optional, if you also want font styles */ + font-style: var(--shiki-dark-font-style) ; + font-weight: var(--shiki-dark-font-weight) ; + text-decoration: var(--shiki-dark-text-decoration) ; + } + + .shiki-ln-line-highlighted, button:hover { + span { + background-color: inherit; + } + } +} + +.shiki { + padding-block: 1rem; +} + +.docs-light-mode .shiki { + color: var(--shiki-light); + background-color: var(--shiki-light-bg) ; + + span { + color: var(--shiki-light) ; + background-color: var(--shiki-light-bg) ; + /* Optional, if you also want font styles */ + font-style: var(--shiki-light-font-style) ; + font-weight: var(--shiki-light-font-weight) ; + text-decoration: var(--shiki-light-text-decoration) ; + } + + .shiki-ln-line-highlighted, button:hover { + span { + background-color: inherit; + } + } +} \ No newline at end of file diff --git a/adev/shared-docs/testing/BUILD.bazel b/adev/shared-docs/testing/BUILD.bazel new file mode 100644 index 000000000000..abda97666268 --- /dev/null +++ b/adev/shared-docs/testing/BUILD.bazel @@ -0,0 +1,30 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "testing", + srcs = [ + "index.ts", + ], + visibility = ["//adev/shared-docs:__subpackages__"], + deps = [ + ":lib", + ], +) + +ts_library( + name = "lib", + srcs = glob( + [ + "*.ts", + ], + exclude = [ + "index.ts", + ], + ), + deps = [ + "//packages/core", + "@npm//@webcontainer/api", + ], +) diff --git a/adev/shared-docs/testing/index.ts b/adev/shared-docs/testing/index.ts new file mode 100644 index 000000000000..cd47c7f7e6b2 --- /dev/null +++ b/adev/shared-docs/testing/index.ts @@ -0,0 +1,9 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './testing-helper'; diff --git a/adev/shared-docs/testing/testing-helper.ts b/adev/shared-docs/testing/testing-helper.ts new file mode 100644 index 000000000000..b7beaba1ea53 --- /dev/null +++ b/adev/shared-docs/testing/testing-helper.ts @@ -0,0 +1,199 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ChangeDetectorRef} from '@angular/core'; +import { + DirEnt, + ErrorListener, + FSWatchCallback, + FSWatchOptions, + FileSystemAPI, + FileSystemTree, + IFSWatcher, + PortListener, + ServerReadyListener, + Unsubscribe, + WebContainer, + WebContainerProcess, +} from '@webcontainer/api'; + +export class FakeEventTarget implements EventTarget { + listeners: Map = new Map(); + + addEventListener(type: string, listener: EventListenerOrEventListenerObject): void { + const listeners = this.listeners.get(type) || []; + listeners.push(listener); + this.listeners.set(type, listeners); + } + + removeEventListener(type: string, listener: EventListenerOrEventListenerObject): void { + const listeners = this.listeners.get(type); + if (listeners) { + const index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + } + } + + dispatchEvent(event: Event): boolean { + const listeners = this.listeners.get(event.type); + if (listeners) { + for (const listener of listeners) { + if (typeof listener === 'function') { + listener.call(this, event); + } else { + listener.handleEvent(event); + } + } + } + return true; + } +} + +export class MockLocalStorage implements Pick { + private items = new Map(); + + getItem(key: string): string | null { + return this.items.get(key) ?? null; + } + + setItem(key: string, value: string | null): void { + this.items.set(key, value); + } +} + +export class FakeChangeDetectorRef implements ChangeDetectorRef { + markForCheck(): void {} + detach(): void {} + checkNoChanges(): void {} + reattach(): void {} + detectChanges(): void {} +} + +export class FakeWebContainer extends WebContainer { + fakeSpawn: FakeWebContainerProcess | undefined = undefined; + + constructor(fakeOptions?: {spawn: FakeWebContainerProcess}) { + super(); + if (fakeOptions?.spawn) this.fakeSpawn = fakeOptions.spawn; + } + + override spawn( + command: unknown, + args?: unknown, + options?: unknown, + ): Promise { + if (this.fakeSpawn) return Promise.resolve(this.fakeSpawn); + + const fakeProcess = new FakeWebContainerProcess(); + + return Promise.resolve(fakeProcess); + } + override on(event: 'port', listener: PortListener): Unsubscribe; + override on(event: 'server-ready', listener: ServerReadyListener): Unsubscribe; + override on(event: 'error', listener: ErrorListener): Unsubscribe; + override on(event: unknown, listener: unknown): Unsubscribe { + return () => {}; + } + override mount( + tree: FileSystemTree, + options?: {mountPoint?: string | undefined} | undefined, + ): Promise { + return Promise.resolve(); + } + override get path() { + return '/fake-path'; + } + override get workdir() { + return '/fake-workdir'; + } + + override teardown() {} + + override fs: FakeFileSystemAPI = new FakeFileSystemAPI(); +} + +class FakeFileSystemAPI implements FileSystemAPI { + readdir( + path: string, + options: 'buffer' | {encoding: 'buffer'; withFileTypes?: false | undefined}, + ): Promise; + readdir( + path: string, + options?: + | string + | {encoding?: string | null | undefined; withFileTypes?: false | undefined} + | null + | undefined, + ): Promise; + readdir( + path: string, + options: {encoding: 'buffer'; withFileTypes: true}, + ): Promise[]>; + readdir( + path: string, + options: {encoding?: string | null | undefined; withFileTypes: true}, + ): Promise[]>; + readdir( + path: unknown, + options?: unknown, + ): + | Promise + | Promise + | Promise[]> + | Promise[]> { + return Promise.resolve(['/fake-dirname']); + } + + readFile(path: string, encoding?: null | undefined): Promise; + readFile(path: string, encoding: string): Promise; + readFile(path: unknown, encoding?: unknown): Promise | Promise { + return Promise.resolve('fake file content'); + } + writeFile( + path: string, + data: string | Uint8Array, + options?: string | {encoding?: string | null | undefined} | null | undefined, + ): Promise { + return Promise.resolve(); + } + mkdir(path: string, options?: {recursive?: false | undefined} | undefined): Promise; + mkdir(path: string, options: {recursive: true}): Promise; + mkdir(path: unknown, options?: unknown): Promise | Promise { + return Promise.resolve(); + } + rm( + path: string, + options?: {force?: boolean | undefined; recursive?: boolean | undefined} | undefined, + ): Promise { + return Promise.resolve(); + } + + rename(oldPath: string, newPath: string): Promise { + throw Error('Not implemented'); + } + watch( + filename: string, + options?: FSWatchOptions | undefined, + listener?: FSWatchCallback | undefined, + ): IFSWatcher; + watch(filename: string, listener?: FSWatchCallback | undefined): IFSWatcher; + watch(filename: unknown, options?: unknown, listener?: unknown): IFSWatcher { + throw Error('Not implemented'); + } +} + +export class FakeWebContainerProcess implements WebContainerProcess { + exit: Promise = Promise.resolve(0); + input: WritableStream = new WritableStream(); + output: ReadableStream = new ReadableStream(); + + kill(): void {} + resize(dimensions: {cols: number; rows: number}): void {} +} diff --git a/adev/shared-docs/utils/BUILD.bazel b/adev/shared-docs/utils/BUILD.bazel new file mode 100644 index 000000000000..133d2d0b348f --- /dev/null +++ b/adev/shared-docs/utils/BUILD.bazel @@ -0,0 +1,36 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "utils", + srcs = [ + "index.ts", + ], + visibility = ["//adev/shared-docs:__subpackages__"], + deps = [ + ":lib", + ], +) + +ts_library( + name = "lib", + srcs = glob( + [ + "**/*.ts", + ], + exclude = [ + "index.ts", + "**/*.spec.ts", + ], + ), + deps = [ + "//adev/shared-docs/interfaces", + "//adev/shared-docs/providers", + "//packages/core", + "//packages/router", + "@npm//@types/node", + "@npm//@webcontainer/api", + "@npm//fflate", + ], +) diff --git a/adev/shared-docs/utils/analytics.utils.ts b/adev/shared-docs/utils/analytics.utils.ts new file mode 100644 index 000000000000..dd3b7796090e --- /dev/null +++ b/adev/shared-docs/utils/analytics.utils.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +declare global { + interface Window { + gtag: (...args: any[]) => void; + } +} + +export const setCookieConsent = (state: 'denied' | 'granted'): void => { + try { + if (window.gtag) { + const consentOptions = { + ad_user_data: state, + ad_personalization: state, + ad_storage: state, + analytics_storage: state, + }; + + if (state === 'denied') { + window.gtag('consent', 'default', { + ...consentOptions, + wait_for_update: 500, + }); + } else if (state === 'granted') { + window.gtag('consent', 'update', { + ...consentOptions, + }); + } + } + } catch { + if (state === 'denied') { + console.error('Unable to set default cookie consent.'); + } else if (state === 'granted') { + console.error('Unable to grant cookie consent.'); + } + } +}; diff --git a/adev/shared-docs/utils/animations.utils.ts b/adev/shared-docs/utils/animations.utils.ts new file mode 100644 index 000000000000..29a0589e9b4e --- /dev/null +++ b/adev/shared-docs/utils/animations.utils.ts @@ -0,0 +1,11 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export const shouldReduceMotion = () => + typeof window !== 'undefined' && + window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true; diff --git a/adev/shared-docs/utils/device.utils.ts b/adev/shared-docs/utils/device.utils.ts new file mode 100644 index 000000000000..dfdd2a94c2d6 --- /dev/null +++ b/adev/shared-docs/utils/device.utils.ts @@ -0,0 +1,25 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export const isMobile = + typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase().includes('mobi'); + +export const isApple = + typeof window !== 'undefined' && + (/iPad|iPhone/.test(window.navigator.userAgent) || window.navigator.userAgent.includes('Mac')); + +export const isIpad = + typeof window !== 'undefined' && + isApple && + !!window.navigator.maxTouchPoints && + window.navigator.maxTouchPoints > 1; + +export const isIos = (isMobile && isApple) || isIpad; + +export const isFirefox = + typeof window !== 'undefined' && window.navigator.userAgent.includes('Firefox/'); diff --git a/adev/shared-docs/utils/filesystem.utils.ts b/adev/shared-docs/utils/filesystem.utils.ts new file mode 100644 index 000000000000..f4e13381fac8 --- /dev/null +++ b/adev/shared-docs/utils/filesystem.utils.ts @@ -0,0 +1,61 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {normalizePath} from './navigation.utils'; +import {FileAndContent} from '../interfaces'; + +interface DirEnt { + name: T; + isFile(): boolean; + isDirectory(): boolean; +} + +interface FileSystemAPI { + readdir( + path: string, + options: { + encoding?: + | 'ascii' + | 'utf8' + | 'utf-8' + | 'utf16le' + | 'ucs2' + | 'ucs-2' + | 'base64' + | 'base64url' + | 'latin1' + | 'binary' + | 'hex' + | null; + withFileTypes: true; + }, + ): Promise[]>; + readFile(path: string, encoding?: string): Promise; +} + +export const checkFilesInDirectory = async ( + dir: string, + fs: FileSystemAPI, + filterFoldersPredicate: (path?: string) => boolean = () => true, + files: FileAndContent[] = [], +) => { + const entries = (await fs.readdir(dir, {withFileTypes: true})) ?? []; + + for (const entry of entries) { + const fullPath = normalizePath(`${dir}/${entry.name}`); + + if (entry.isFile()) { + const content = await fs.readFile(fullPath, 'utf-8'); + files.push({content, path: fullPath}); + } else if (entry.isDirectory() && filterFoldersPredicate(entry.name)) { + await checkFilesInDirectory(fullPath, fs, filterFoldersPredicate, files); + } + } + + return files; +}; diff --git a/adev/shared-docs/utils/index.ts b/adev/shared-docs/utils/index.ts new file mode 100644 index 000000000000..2bdd30eeb6ae --- /dev/null +++ b/adev/shared-docs/utils/index.ts @@ -0,0 +1,15 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export * from './analytics.utils'; +export * from './animations.utils'; +export * from './device.utils'; +export * from './filesystem.utils'; +export * from './navigation.utils'; +export * from './url.utils'; +export * from './zip.utils'; diff --git a/adev/shared-docs/utils/navigation.utils.ts b/adev/shared-docs/utils/navigation.utils.ts new file mode 100644 index 000000000000..853ca5fad3cf --- /dev/null +++ b/adev/shared-docs/utils/navigation.utils.ts @@ -0,0 +1,156 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {inject} from '@angular/core'; +import {ActivatedRouteSnapshot, Route, Router} from '@angular/router'; +import {NavigationItem} from '../interfaces/index'; +import {DOCS_CONTENT_LOADER} from '../providers/index'; + +export const flatNavigationData = (tree: NavigationItem[]) => { + const result: NavigationItem[] = []; + + const traverse = (node: NavigationItem, level: number) => { + node.level = level; + if (node.path) { + result.push(node); + } + if (node.children) { + for (const child of node.children) { + child.parent = node; + traverse(child, level + 1); + } + } + }; + + for (const node of tree) { + traverse(node, 1); + } + + return result; +}; + +export const getNavigationItemsTree = ( + tree: NavigationItem[], + mapFn: (item: NavigationItem) => void, +) => { + const traverse = (node: NavigationItem) => { + mapFn(node); + if (node.children) { + for (const child of node.children) { + traverse(child); + } + } + }; + + for (const node of tree) { + traverse(node); + } + + return tree; +}; + +export const findNavigationItem = ( + items: NavigationItem[], + predicate: (item: NavigationItem) => boolean, +): NavigationItem | null => { + let result: NavigationItem | null = null; + + const traverse = (node: NavigationItem) => { + if (predicate(node)) { + result = node; + } + if (node.children && !result) { + for (const child of node.children) { + traverse(child); + } + } + }; + + for (const node of items) { + traverse(node); + } + + return result; +}; + +export const isExternalLink = (link: string, windowOrigin: string) => + new URL(link).origin !== windowOrigin; + +export const markExternalLinks = (item: NavigationItem, origin: string): void => { + if (item.path) { + try { + item.isExternal = isExternalLink(item.path, origin); + } catch (err) {} + } +}; + +export const mapNavigationItemsToRoutes = ( + navigationItems: NavigationItem[], + additionalRouteProperties: Partial, +): Route[] => + navigationItems + .filter((route): route is NavigationItem & {path: string} => Boolean(route.path)) + .map((navigationItem) => { + const route = { + path: navigationItem.path, + ...additionalRouteProperties, + }; + + route.data = { + ...navigationItem, + ...route.data, + }; + + route.resolve = { + 'docContent': (snapshot: ActivatedRouteSnapshot) => { + return snapshot.data['contentPath'] !== undefined + ? inject(DOCS_CONTENT_LOADER).getContent(snapshot.data['contentPath']) + : undefined; + }, + ...route.resolve, + }; + return route; + }); + +export const normalizePath = (path: string): string => { + if (path[0] === '/') { + return path.substring(1); + } + return path; +}; + +export const getBaseUrlAfterRedirects = (url: string, router: Router): string => { + const route = router.parseUrl(url); + route.fragment = null; + route.queryParams = {}; + return normalizePath(route.toString()); +}; + +export function handleHrefClickEventWithRouter(e: Event, router: Router, relativeUrl: string) { + const pointerEvent = e as PointerEvent; + if ( + pointerEvent.ctrlKey || + pointerEvent.shiftKey || + pointerEvent.altKey || + pointerEvent.metaKey + ) { + return; + } + + e.preventDefault(); + router.navigateByUrl(relativeUrl); +} + +export function getActivatedRouteSnapshotFromRouter(router: Router): ActivatedRouteSnapshot { + let route = router.routerState.root.snapshot; + + while (route.firstChild) { + route = route.firstChild; + } + return route; +} diff --git a/adev/shared-docs/utils/url.utils.ts b/adev/shared-docs/utils/url.utils.ts new file mode 100644 index 000000000000..14bee4d5411c --- /dev/null +++ b/adev/shared-docs/utils/url.utils.ts @@ -0,0 +1,14 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export const removeTrailingSlash = (url: string): string => { + if (url.endsWith('/')) { + return url.slice(0, -1); + } + return url; +}; diff --git a/adev/shared-docs/utils/zip.utils.ts b/adev/shared-docs/utils/zip.utils.ts new file mode 100644 index 000000000000..ee005aa21e27 --- /dev/null +++ b/adev/shared-docs/utils/zip.utils.ts @@ -0,0 +1,29 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {FileAndContent} from '../interfaces'; + +// TODO(josephperrott): Determine how we can load the fflate package dynamically again. +import {zip, strToU8} from 'fflate'; + +export async function generateZip(files: FileAndContent[]): Promise { + const filesObj: Record = {}; + files.forEach(({path, content}) => { + filesObj[path] = typeof content === 'string' ? strToU8(content) : content; + }); + + return new Promise((resolve, reject) => { + zip(filesObj, (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); +} diff --git a/adev/src/app/app.config.server.ts b/adev/src/app/app.config.server.ts index 67014d6003d5..d0031646155d 100644 --- a/adev/src/app/app.config.server.ts +++ b/adev/src/app/app.config.server.ts @@ -9,16 +9,9 @@ import {mergeApplicationConfig, ApplicationConfig} from '@angular/core'; import {provideServerRendering} from '@angular/platform-server'; import {appConfig} from './app.config'; -import { - ReferenceScrollHandler, - ReferenceScrollHandlerNoop, -} from './features/references/services/reference-scroll-handler.service'; const serverConfig: ApplicationConfig = { - providers: [ - provideServerRendering(), - {provide: ReferenceScrollHandler, useClass: ReferenceScrollHandlerNoop}, - ], + providers: [provideServerRendering()], }; export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/adev/src/app/app.config.ts b/adev/src/app/app.config.ts index 1bc572a0305b..5e0bee2c2a3f 100644 --- a/adev/src/app/app.config.ts +++ b/adev/src/app/app.config.ts @@ -32,6 +32,7 @@ import { TitleStrategy, createUrlTreeFromSnapshot, provideRouter, + withComponentInputBinding, withInMemoryScrolling, withViewTransitions, } from '@angular/router'; @@ -44,7 +45,6 @@ import {CustomErrorHandler} from './core/services/errors-handling/error-handler' import {ExampleContentLoader} from './core/services/example-content-loader.service'; import {ReuseTutorialsRouteStrategy} from './features/tutorial/tutorials-route-reuse-strategy'; import {routes} from './routes'; -import {ReferenceScrollHandler} from './features/references/services/reference-scroll-handler.service'; import {CURRENT_MAJOR_VERSION} from './core/providers/current-version'; import {AppScroller} from './app-scroller'; @@ -70,6 +70,7 @@ export const appConfig: ApplicationConfig = { } }, }), + withComponentInputBinding(), ), provideExperimentalZonelessChangeDetection(), provideClientHydration(), @@ -104,6 +105,5 @@ export const appConfig: ApplicationConfig = { deps: [DOCUMENT], }, {provide: TitleStrategy, useClass: ADevTitleStrategy}, - ReferenceScrollHandler, ], }; diff --git a/adev/src/app/core/layout/progress-bar/progress-bar.component.spec.ts b/adev/src/app/core/layout/progress-bar/progress-bar.component.spec.ts index cfbac712a013..317d3cfa1d7f 100644 --- a/adev/src/app/core/layout/progress-bar/progress-bar.component.spec.ts +++ b/adev/src/app/core/layout/progress-bar/progress-bar.component.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {PROGRESS_BAR_DELAY, ProgressBarComponent} from './progress-bar.component'; import {RouterTestingHarness, RouterTestingModule} from '@angular/router/testing'; @@ -25,13 +25,13 @@ describe('ProgressBarComponent', () => { fixture.detectChanges(); }); - it('should call progressBar.complete() on route change', fakeAsync(async () => { + it('should call progressBar.complete() on route change', async () => { const progressBarCompleteSpy = spyOn(component.progressBar, 'complete'); const harness = await RouterTestingHarness.create(); await harness.navigateByUrl('/'); - tick(PROGRESS_BAR_DELAY); + await new Promise((resolve) => setTimeout(resolve, PROGRESS_BAR_DELAY)); expect(progressBarCompleteSpy).toHaveBeenCalled(); - })); + }); }); diff --git a/adev/src/app/core/services/errors-handling/error-snack-bar.ts b/adev/src/app/core/services/errors-handling/error-snack-bar.ts index 935a0102afd4..d9779d71b74c 100644 --- a/adev/src/app/core/services/errors-handling/error-snack-bar.ts +++ b/adev/src/app/core/services/errors-handling/error-snack-bar.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Inject, inject} from '@angular/core'; import {MAT_SNACK_BAR_DATA, MatSnackBarAction, MatSnackBarRef} from '@angular/material/snack-bar'; export interface ErrorSnackBarData { @@ -37,10 +37,8 @@ export class ErrorSnackBar { protected message: string; protected actionText?: string; - constructor( - protected snackBarRef: MatSnackBarRef, - @Inject(MAT_SNACK_BAR_DATA) public data: ErrorSnackBarData, - ) { + constructor(protected snackBarRef: MatSnackBarRef) { + const data = inject(MAT_SNACK_BAR_DATA) as ErrorSnackBarData; this.message = data.message; this.actionText = data.actionText; } diff --git a/adev/src/app/core/services/header.service.spec.ts b/adev/src/app/core/services/header.service.spec.ts index c9033b6733ea..73e822d15389 100644 --- a/adev/src/app/core/services/header.service.spec.ts +++ b/adev/src/app/core/services/header.service.spec.ts @@ -9,7 +9,6 @@ import {TestBed} from '@angular/core/testing'; import {HeaderService} from './header.service'; -import {link} from 'fs'; describe('HeaderService', () => { let service: HeaderService; diff --git a/adev/src/app/core/services/version-manager.service.ts b/adev/src/app/core/services/version-manager.service.ts index 4b0fcce3350c..3da3517a8f84 100644 --- a/adev/src/app/core/services/version-manager.service.ts +++ b/adev/src/app/core/services/version-manager.service.ts @@ -68,7 +68,7 @@ export class VersionManager { // version: 'rc', // }, { - url: this.getAdevDocsUrl(this.currentMajorVersion), + url: 'https://angular.dev/', displayName: this.getVersion(this.currentMajorVersion), version: this.currentVersionMode, }, diff --git a/adev/src/app/editor/code-editor/code-editor.component.html b/adev/src/app/editor/code-editor/code-editor.component.html index 7281d5c04b5c..06b87384d0b0 100644 --- a/adev/src/app/editor/code-editor/code-editor.component.html +++ b/adev/src/app/editor/code-editor/code-editor.component.html @@ -65,6 +65,26 @@
+ + + +
+ + +
+
``` -```html +```angular-html

Your profile photo

@@ -41,7 +41,7 @@ A component can bind properties, attributes, and events to its host element. Thi identically to bindings on elements inside the component's template, but instead defined with the `host` property in the `@Component` decorator: -```ts +```angular-ts @Component({ ..., host: { @@ -68,7 +68,7 @@ decorator to class members. `@HostBinding` lets you bind host properties and attributes to properties and methods: -```ts +```angular-ts @Component({ /* ... */ }) @@ -105,7 +105,7 @@ decorators exist exclusively for backwards compatibility. When you use a component in a template, you can add bindings to that component instance's element. The component may _also_ define host bindings for the same properties or attributes. -```ts +```angular-ts @Component({ ..., host: { @@ -116,7 +116,7 @@ The component may _also_ define host bindings for the same properties or attribu export class ProfilePhoto { /* ... */ } ``` -```html +```angular-html ``` diff --git a/adev/src/content/guide/components/importing.md b/adev/src/content/guide/components/importing.md index a3bbf85b1ade..7978df771dbc 100644 --- a/adev/src/content/guide/components/importing.md +++ b/adev/src/content/guide/components/importing.md @@ -10,7 +10,7 @@ A **standalone component** is a component that sets `standalone: true` in its co Standalone components directly import other components, directives, and pipes used in their templates: - + @Component({ standalone: true, selector: 'profile-photo', diff --git a/adev/src/content/guide/components/inheritance.md b/adev/src/content/guide/components/inheritance.md index 3bed268a769b..150c54007a0a 100644 --- a/adev/src/content/guide/components/inheritance.md +++ b/adev/src/content/guide/components/inheritance.md @@ -24,7 +24,7 @@ When a component extends another component or a directive, it inherits all the m the base class's decorator and the base class's decorated members. This includes the selector, template, styles, host bindings, inputs, outputs, lifecycle methods, and any other settings. -```ts +```angular-ts @Component({ selector: 'base-listbox', template: ` diff --git a/adev/src/content/guide/components/inputs.md b/adev/src/content/guide/components/inputs.md index 8e639367542f..164c673ca24b 100644 --- a/adev/src/content/guide/components/inputs.md +++ b/adev/src/content/guide/components/inputs.md @@ -15,7 +15,7 @@ export class CustomSlider { This lets you bind to the property in a template: -```html +```angular-html ``` @@ -62,7 +62,7 @@ function trimString(value: string | undefined) { } -```html +```angular-html ``` @@ -121,7 +121,7 @@ export class CustomSlider { } -```html +```angular-html ``` diff --git a/adev/src/content/guide/components/output-function.md b/adev/src/content/guide/components/output-function.md index 772317825f1e..77e83a187eae 100644 --- a/adev/src/content/guide/components/output-function.md +++ b/adev/src/content/guide/components/output-function.md @@ -21,7 +21,7 @@ export class MyComp { An output is automatically recognized by Angular whenever you use the `output` function as an initializer of a class member. Parent components can listen to outputs in templates by using the event binding syntax. -```html +```angular-html ``` diff --git a/adev/src/content/guide/components/outputs.md b/adev/src/content/guide/components/outputs.md index 20942f61b2dc..140cb1971ea5 100644 --- a/adev/src/content/guide/components/outputs.md +++ b/adev/src/content/guide/components/outputs.md @@ -11,7 +11,7 @@ export class ExpandablePanel { }
-```html +```angular-html ``` @@ -46,7 +46,7 @@ this.thumbDropped.emit({ When defining an event listener in a template, you can access the event data from the `$event` variable: -```html +```angular-html ``` @@ -61,7 +61,7 @@ export class CustomSlider { }
-```html +```angular-html ``` diff --git a/adev/src/content/guide/components/programmatic-rendering.md b/adev/src/content/guide/components/programmatic-rendering.md index 29c30d3377c3..52eb65267d5f 100644 --- a/adev/src/content/guide/components/programmatic-rendering.md +++ b/adev/src/content/guide/components/programmatic-rendering.md @@ -11,7 +11,7 @@ or in your TypeScript code with `ViewContainerRef`. `NgComponentOutlet` is a structural directive that dynamically renders a given component in a template. -```ts +```angular-ts @Component({ ... }) export class AdminBio { /* ... */ } @@ -46,7 +46,7 @@ You can use the `createComponent`method on `ViewContainerRef` to dynamically cre component. When you create a new component with a `ViewContainerRef`, Angular appends it into the DOM as the next sibling of the component or directive that injected the `ViewContainerRef`. -```ts +```angular-ts @Component({ selector: 'leaf-content', template: ` @@ -82,7 +82,7 @@ export class InnerItem { In the example above, clicking the "Load content" button results in the following DOM structure -```html +```angular-html

This is the start of the outer container

@@ -99,7 +99,7 @@ You can use both of the approaches described above, `NgComponentOutlet` and `Vie render components that are lazy-loaded with a standard JavaScript [dynamic import](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import). -```ts +```angular-ts @Component({ ..., template: ` @@ -113,7 +113,8 @@ JavaScript [dynamic import](https://developer.mozilla.org/docs/Web/JavaScript/Re Load advanced settings - ` + + ` }) export class AdminSettings { advancedSettings: {new(): AdminSettings} | undefined; diff --git a/adev/src/content/guide/components/queries.md b/adev/src/content/guide/components/queries.md index 9fd9ceba58cb..bbb27f62311c 100644 --- a/adev/src/content/guide/components/queries.md +++ b/adev/src/content/guide/components/queries.md @@ -12,7 +12,7 @@ There are two categories of query: **view queries** and **content queries.** View queries retrieve results from the elements in the component's _view_ — the elements defined in the component's own template. You can query for a single result with the `@ViewChild` decorator. - + @Component({ selector: 'custom-card-header', ... @@ -42,7 +42,7 @@ If the query does not find a result, its value is `undefined`. This may occur if You can also query for multiple results with the `@ViewChildren` decorator. - + @Component({ selector: 'custom-card-action', ..., @@ -77,7 +77,7 @@ export class CustomCard { Content queries retrieve results from the elements in the component's _content_— the elements nested inside the component in the template where it's used. You can query for a single result with the `@ContentChild` decorator. - + @Component({ selector: 'custom-toggle', ... @@ -106,6 +106,7 @@ export class CustomExpando { ` }) +export class UserProfile { } In this example, the `CustomExpando` component queries for a child `CustomToggle` and accesses the result in `ngAfterContentInit`. @@ -118,7 +119,7 @@ By default, content queries find only _direct_ children of the component and do You can also query for multiple results with the `@ContentChildren` decorator. - + @Component({ selector: 'custom-menu-item', ... @@ -150,6 +151,7 @@ export class CustomMenu { ` }) +export class UserProfile { } `@ContentChildren` creates a `QueryList` object that contains the query results. You can subscribe to changes to the query results over time via the `changes` property. @@ -165,7 +167,7 @@ Most of the time, you want to use a component or directive as your locator. You can alternatively specify a string locator corresponding to a [template reference variable](guide/templates/reference-variables). -```ts +```angular-ts @Component({ ..., template: ` @@ -188,7 +190,7 @@ Tip: See [Dependency Injection](guide/di) for background on providers and Angula For more advanced cases, you can use any `ProviderToken` as a locator. This lets you locate elements based on component and directive providers. -```ts +```angular-ts const SUB_ITEM = new InjectionToken('sub-item'); @Component({ @@ -213,7 +215,7 @@ All query decorators accept an options object as a second parameter. These optio `@ViewChild` and `@ContentChild` queries accept the `static` option. -```ts +```angular-ts @Component({ selector: 'custom-card', template: 'Visit sunny California!', @@ -237,7 +239,7 @@ The `static` option is not available for `@ViewChildren` and `@ContentChildren` By default, content queries find only _direct_ children of the component and do not traverse into descendants. - + @Component({ selector: 'custom-expando', ... @@ -257,6 +259,7 @@ export class CustomExpando { ` }) +export class UserProfile { } In the example above, `CustomExpando` cannot find `` because it is not a direct child of ``. By setting `descendants: true`, you configure the query to traverse all descendants in the same template. Queries, however, _never_ pierce into components to traverse elements in other templates. diff --git a/adev/src/content/guide/components/selectors.md b/adev/src/content/guide/components/selectors.md index 73862ba3a5bf..6bc8c20d417d 100644 --- a/adev/src/content/guide/components/selectors.md +++ b/adev/src/content/guide/components/selectors.md @@ -6,7 +6,7 @@ Every component defines a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_selectors) that determines how the component is used: - + @Component({ selector: 'profile-photo', ... @@ -16,7 +16,7 @@ export class ProfilePhoto { } You use a component by creating a matching HTML element in the templates of _other_ components: - + @Component({ template: ` @@ -63,7 +63,7 @@ You can append this pseudo-class to any other selector to narrow which elements selector matches. For example, you could define a `[dropzone]` attribute selector and prevent matching `textarea` elements: - + @Component({ selector: '[dropzone]:not(textarea)', ... @@ -78,7 +78,7 @@ Angular does not support any other pseudo-classes or pseudo-elements in componen You can combine multiple selectors by concatenating them. For example, you can match ` @defer (on interaction(greeting)) { @@ -182,7 +182,7 @@ Alternatively, you can specify a [template reference variable](guide/templates/r By default, the placeholder will act as the hover element as long as it is a single root element node. -```html +```angular-html @defer (on hover) { } @placeholder { @@ -192,7 +192,7 @@ By default, the placeholder will act as the hover element as long as it is a sin Alternatively, you can specify a [template reference variable](guide/templates/reference-variables) as the hover element. This variable is passed in as a parameter on the hover trigger. -```html +```angular-html
Hello!
@defer (on hover(greeting)) { @@ -206,7 +206,7 @@ Alternatively, you can specify a [template reference variable](guide/templates/r `immediate` triggers the deferred load immediately, meaning once the client has finished rendering, the defer chunk would then start fetching right away. -```html +```angular-html @defer (on immediate) { } @placeholder { @@ -218,7 +218,7 @@ Alternatively, you can specify a [template reference variable](guide/templates/r `timer(x)` would trigger after a specified duration. The duration is required and can be specified in `ms` or `s`. -```html +```angular-html @defer (on timer(500ms)) { } @@ -232,7 +232,7 @@ In this case, `when` and `on` associated with defer controls when to render, and In the example below, the prefetching starts when a browser becomes idle and the contents of the block is rendered on interaction. -```html +```angular-html @defer (on interaction; prefetch on idle) { } @placeholder { diff --git a/adev/src/content/guide/di/BUILD.bazel b/adev/src/content/guide/di/BUILD.bazel index 61af7bc13447..a4e039501aca 100644 --- a/adev/src/content/guide/di/BUILD.bazel +++ b/adev/src/content/guide/di/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "di", diff --git a/adev/src/content/guide/directives/BUILD.bazel b/adev/src/content/guide/directives/BUILD.bazel index e9dffa9828bd..f987e7f3f0f5 100644 --- a/adev/src/content/guide/directives/BUILD.bazel +++ b/adev/src/content/guide/directives/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "directives", diff --git a/adev/src/content/guide/directives/attribute-directives.md b/adev/src/content/guide/directives/attribute-directives.md index fd391d91deda..46b91a2d4eeb 100644 --- a/adev/src/content/guide/directives/attribute-directives.md +++ b/adev/src/content/guide/directives/attribute-directives.md @@ -143,12 +143,12 @@ To prevent expression evaluation in the browser, add `ngNonBindable` to the host In the following example, the expression `{{ 1 + 1 }}` renders just as it does in your code editor, and does not display `2`. - + Applying `ngNonBindable` to an element stops binding for that element's child elements. However, `ngNonBindable` still lets directives work on the element where you apply `ngNonBindable`. In the following example, the `appHighlight` directive is still active but Angular does not evaluate the expression `{{ 1 + 1 }}`. - + If you apply `ngNonBindable` to a parent element, Angular disables interpolation and binding of any sort, such as property binding or event binding, for the element's children. diff --git a/adev/src/content/guide/directives/directive-composition-api.md b/adev/src/content/guide/directives/directive-composition-api.md index 11b870baf09c..bfb37cf50d78 100644 --- a/adev/src/content/guide/directives/directive-composition-api.md +++ b/adev/src/content/guide/directives/directive-composition-api.md @@ -59,7 +59,7 @@ export class AdminMenu { } By explicitly specifying the inputs and outputs, consumers of the component with `hostDirective` can bind them in a template: -```html +```angular-html ``` @@ -81,7 +81,7 @@ component: export class AdminMenu { } ``` -```html +```angular-html ``` diff --git a/adev/src/content/guide/directives/structural-directives.md b/adev/src/content/guide/directives/structural-directives.md index f0e7bea62ebf..08be31f42e69 100644 --- a/adev/src/content/guide/directives/structural-directives.md +++ b/adev/src/content/guide/directives/structural-directives.md @@ -10,7 +10,7 @@ In this guide you'll build a structural directive which fetches data from a give The following is an example of using this directive directly on an `` would look like: -```html +```angular-html

The data is: {{ data }}

@@ -30,7 +30,7 @@ Structural directives can be applied directly on an element by prefixing the dir You can use this with `SelectDirective` as follows: -```html +```angular-html

The data is: {{data}}

``` @@ -38,7 +38,7 @@ This example shows the flexibility of structural directive shorthand syntax, whi When used in this way, only the structural directive and its bindings are applied to the ``. Any other attributes or bindings on the `

` tag are left alone. For example, these two forms are equivalent: -```html +```angular-html

The data is: {{data}}

@@ -80,7 +80,7 @@ import {Directive, TemplateRef, ViewContainerRef} from '@angular/core'; @Directive({ standalone: true, - selector: 'select', + selector: '[select]', }) export class SelectDirective { constructor(private templateRef: TemplateRef, private ViewContainerRef: ViewContainerRef) {} diff --git a/adev/src/content/guide/forms/BUILD.bazel b/adev/src/content/guide/forms/BUILD.bazel index 1fd26759b540..68cc9611c7ba 100644 --- a/adev/src/content/guide/forms/BUILD.bazel +++ b/adev/src/content/guide/forms/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "forms", diff --git a/adev/src/content/guide/forms/typed-forms.md b/adev/src/content/guide/forms/typed-forms.md index 6ddce0cbe205..6b6b5ed9b7ed 100644 --- a/adev/src/content/guide/forms/typed-forms.md +++ b/adev/src/content/guide/forms/typed-forms.md @@ -12,8 +12,8 @@ With Angular reactive forms, you explicitly specify a *form model*. As a simple ```ts const login = new FormGroup({ - email: new FormControl(''), - password: new FormControl(''), + email: new FormControl(''), + password: new FormControl(''), }); ``` @@ -37,8 +37,8 @@ Non-typed forms are still supported, and will continue to work as before. To use ```ts const login = new UntypedFormGroup({ - email: new UntypedFormControl(''), - password: new UntypedFormControl(''), + email: new UntypedFormControl(''), + password: new UntypedFormControl(''), }); ``` @@ -132,13 +132,13 @@ Some forms have controls that may or may not be present, which can be added and ```ts interface LoginForm { - email: FormControl; - password?: FormControl; + email: FormControl; + password?: FormControl; } const login = new FormGroup({ - email: new FormControl('', {nonNullable: true}), - password: new FormControl('', {nonNullable: true}), + email: new FormControl('', {nonNullable: true}), + password: new FormControl('', {nonNullable: true}), }); login.removeControl('password'); @@ -174,8 +174,8 @@ Additionally, an additional builder is available: `NonNullableFormBuilder`. This ```ts const fb = new FormBuilder(); const login = fb.nonNullable.group({ - email: '', - password: '', + email: '', + password: '', }); ``` diff --git a/adev/src/content/guide/http/BUILD.bazel b/adev/src/content/guide/http/BUILD.bazel index 42c82e3257bc..2099955305d7 100644 --- a/adev/src/content/guide/http/BUILD.bazel +++ b/adev/src/content/guide/http/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "http", diff --git a/adev/src/content/guide/hydration.md b/adev/src/content/guide/hydration.md index 6fb0166d48f3..966d16ae2559 100644 --- a/adev/src/content/guide/hydration.md +++ b/adev/src/content/guide/hydration.md @@ -130,7 +130,7 @@ For a full reference on hydration related errors, visit the [Errors Reference Gu Some components may not work properly with hydration enabled due to some of the aforementioned issues, like [Direct DOM Manipulation](#direct-dom-manipulation). As a workaround, you can add the `ngSkipHydration` attribute to a component's tag in order to skip hydrating the entire component. -```html +```angular-html ``` diff --git a/adev/src/content/guide/i18n/BUILD.bazel b/adev/src/content/guide/i18n/BUILD.bazel index 35ab545a04c0..b3d124e1ee63 100644 --- a/adev/src/content/guide/i18n/BUILD.bazel +++ b/adev/src/content/guide/i18n/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "i18n", diff --git a/adev/src/content/guide/image-optimization.md b/adev/src/content/guide/image-optimization.md index 232e8d28cde2..44c82a78cdac 100644 --- a/adev/src/content/guide/image-optimization.md +++ b/adev/src/content/guide/image-optimization.md @@ -52,7 +52,7 @@ A brief guide for setting up a loader can be found in the [Configuring an Image To activate the `NgOptimizedImage` directive, replace your image's `src` attribute with `ngSrc`. - + @@ -63,7 +63,7 @@ If you're using a [built-in third-party loader](#built-in-loaders), make sure to Always mark the [LCP image](https://web.dev/lcp/#what-elements-are-considered) on your page as `priority` to prioritize its loading. - + @@ -80,7 +80,7 @@ Angular displays a warning during development if the LCP element is an image tha In order to prevent [image-related layout shifts](https://web.dev/css-web-vitals/#images-and-layout-shifts), NgOptimizedImage requires that you specify a height and width for your image, as follows: - + @@ -100,7 +100,7 @@ In cases where you want to have an image fill a containing element, you can use When you add the `fill` attribute to your image, you do not need and should not include a `width` and `height`, as in this example: - + @@ -133,7 +133,7 @@ You can adjust how the background image fills the container as described in the NgOptimizedImage can display an automatic low-resolution placeholder for your image if you're using a CDN or image host that provides automatic image resizing. Take advantage of this feature by adding the `placeholder` attribute to your image: - + @@ -160,9 +160,14 @@ If you want sharp edges around your blurred placeholder, you can wrap your image You can also specify a placeholder using a base64 [data URL](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URLs) without an image loader. The data url format is `data:image/[imagetype];[data]`, where `[imagetype]` is the image format, just as `png`, and `[data]` is a base64 encoding of the image. That encoding can be done using the command line or in JavaScript. For specific commands, see [the MDN documentation](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URLs#encoding_data_into_base64_format). An example of a data URL placeholder with truncated data is shown below: - + - + @@ -172,9 +177,15 @@ However, large data URLs increase the size of your Angular bundles and slow dow By default, NgOptimizedImage applies a CSS blur effect to image placeholders. To render a placeholder without blur, provide a `placeholderConfig` argument with an object that includes the `blur` property, set to false. For example: - + - + @@ -224,7 +235,10 @@ Defining a [`srcset` attribute](https://developer.mozilla.org/docs/Web/API/HTMLI If your image should be "fixed" in size (i.e. the same size across devices, except for [pixel density](https://web.dev/codelab-density-descriptors/)), there is no need to set a `sizes` attribute. A `srcset` can be generated automatically from the image's width and height attributes with no further input required. -Example srcset generated: `` +Example srcset generated: +```angular-html + +``` #### Responsive images @@ -253,7 +267,7 @@ providers: [ If you would like to manually define a `srcset` attribute, you can provide your own using the `ngSrcset` attribute: - + @@ -261,7 +275,7 @@ If you would like to manually define a `srcset` attribute, you can provide your If the `ngSrcset` attribute is present, `NgOptimizedImage` generates and sets the `srcset` based on the sizes included. Do not include image file names in `ngSrcset` - the directive infers this information from `ngSrc`. The directive supports both width descriptors (e.g. `100w`) and density descriptors (e.g. `1x`). - + @@ -271,7 +285,7 @@ If the `ngSrcset` attribute is present, `NgOptimizedImage` generates and sets th To disable srcset generation for a single image, you can add the `disableOptimizedSrcset` attribute on the image: - + @@ -281,7 +295,7 @@ To disable srcset generation for a single image, you can add the `disableOptimiz By default, `NgOptimizedImage` sets `loading=lazy` for all images that are not marked `priority`. You can disable this behavior for non-priority images by setting the `loading` attribute. This attribute accepts values: `eager`, `auto`, and `lazy`. [See the documentation for the standard image `loading` attribute for details](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/loading#value). - + @@ -291,7 +305,7 @@ By default, `NgOptimizedImage` sets `loading=lazy` for all images that are not m You may want to have images displayed at varying widths on differently-sized screens. A common example of this pattern is a grid- or column-based layout that renders a single column on mobile devices, and two columns on larger devices. You can capture this behavior in the `sizes` attribute, using a "media query" syntax, such as the following: - + @@ -388,7 +402,7 @@ const myCustomLoader = (config: ImageLoaderConfig) => { Note that in the above example, we've invented the 'roundedCorners' property name to control a feature of our custom loader. We could then use this feature when creating an image, as follows: - + diff --git a/adev/src/content/guide/ngmodules/BUILD.bazel b/adev/src/content/guide/ngmodules/BUILD.bazel index a31a3e22561d..52abb97fde44 100644 --- a/adev/src/content/guide/ngmodules/BUILD.bazel +++ b/adev/src/content/guide/ngmodules/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "ngmodules", diff --git a/adev/src/content/guide/performance/BUILD.bazel b/adev/src/content/guide/performance/BUILD.bazel index 6df8d48d5422..c2a80708eea0 100644 --- a/adev/src/content/guide/performance/BUILD.bazel +++ b/adev/src/content/guide/performance/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "performance", diff --git a/adev/src/content/guide/performance/overview.md b/adev/src/content/guide/performance/overview.md index ff1b8ee3f2e7..8fa027efd489 100644 --- a/adev/src/content/guide/performance/overview.md +++ b/adev/src/content/guide/performance/overview.md @@ -8,8 +8,8 @@ That said, please note that these best practices will only take the performance | Guides Types | Description | | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | -| [Deferrable views](/defer) | Defer loading of select dependencies within a template by wrapping corresponding parts in a `@defer` block. | -| [Image optimization](/image-optimization) | Use the `NgOptimizedImage` directive to adopt best practices for loading images. | -| [Server-side rendering](/ssr) | Learn how to leverage rendering pages on the server to improve load times. | -| [Build-time prerendering](/prerendering) | Also known as static-side generation (SSG), is an alternate rendering method to improve load times. | -| [Hydration](/hydration) | A process to improve application performance by restoring its state after server-side rendering and reusing existing DOM structure as much as possible. | +| [Deferrable views](/guide/defer) | Defer loading of select dependencies within a template by wrapping corresponding parts in a `@defer` block. | +| [Image optimization](/guide/image-optimization) | Use the `NgOptimizedImage` directive to adopt best practices for loading images. | +| [Server-side rendering](/guide/ssr) | Learn how to leverage rendering pages on the server to improve load times. | +| [Build-time prerendering](/guide/prerendering) | Also known as static-side generation (SSG), is an alternate rendering method to improve load times. | +| [Hydration](/guide/hydration) | A process to improve application performance by restoring its state after server-side rendering and reusing existing DOM structure as much as possible. | diff --git a/adev/src/content/guide/pipes/BUILD.bazel b/adev/src/content/guide/pipes/BUILD.bazel index ceb742e58294..794cba9db68c 100644 --- a/adev/src/content/guide/pipes/BUILD.bazel +++ b/adev/src/content/guide/pipes/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "pipes", diff --git a/adev/src/content/guide/pipes/overview.md b/adev/src/content/guide/pipes/overview.md index 833e90c2be41..60380d7c74eb 100644 --- a/adev/src/content/guide/pipes/overview.md +++ b/adev/src/content/guide/pipes/overview.md @@ -23,5 +23,5 @@ The following are commonly used built-in pipes for data formatting: - [`AsyncPipe`](api/common/AsyncPipe): Subscribe and unsubscribe to an asynchronous source such as an observable. - [`JsonPipe`](api/common/JsonPipe): Display a component object property to the screen as JSON for debugging. -Note: For a complete list of built-in pipes, see the [pipes API documentation](/api/common#pipes "Pipes API reference summary"). +Note: For a complete list of built-in pipes, see the [pipes API documentation](/api?type=pipe "Pipes API reference summary"). To learn more about using pipes for internationalization (i18n) efforts, see [formatting data based on locale](guide/i18n/format-data-locale). diff --git a/adev/src/content/guide/pipes/template.md b/adev/src/content/guide/pipes/template.md index 2151c1a8ce0e..ae5f7d32dfeb 100644 --- a/adev/src/content/guide/pipes/template.md +++ b/adev/src/content/guide/pipes/template.md @@ -2,7 +2,7 @@ To apply a pipe, use the pipe operator (`|`) within a template expression as shown in the following code example. - +

The hero's birthday is {{ birthday | date }}

@@ -30,7 +30,7 @@ Pipes can take additional parameters that configure the transformation. Paramete For example, the `date` pipe takes optional parameters that control the date's display format. To specify the parameter, follow the pipe name with a colon (`:`) and the parameter value (the format). - +

The hero's birthday is in {{ birthday | date:'yyyy' }}

@@ -49,7 +49,7 @@ You can connect multiple pipes so that the output of one pipe becomes the input The following example passes a date to the `DatePipe` and then forwards the result to the [`UpperCasePipe`](api/common/UpperCasePipe 'API reference') pipe. - +

The hero's birthday is {{ birthday | date }}

The hero's birthday is in {{ birthday | date:'yyyy' | uppercase }}

diff --git a/adev/src/content/guide/routing/BUILD.bazel b/adev/src/content/guide/routing/BUILD.bazel index 17de257bc73c..2873629196b8 100644 --- a/adev/src/content/guide/routing/BUILD.bazel +++ b/adev/src/content/guide/routing/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/docs:index.bzl", "generate_guides") +load("//adev/shared-docs:index.bzl", "generate_guides") generate_guides( name = "routing", diff --git a/adev/src/content/guide/routing/common-router-tasks.md b/adev/src/content/guide/routing/common-router-tasks.md index dcc8b30a6106..aa1907fcebdf 100644 --- a/adev/src/content/guide/routing/common-router-tasks.md +++ b/adev/src/content/guide/routing/common-router-tasks.md @@ -93,7 +93,7 @@ const routes: Routes = [ Now that you have defined your routes, add them to your application. First, add links to the two components. Assign the anchor tag that you want to add the route to the `routerLink` attribute. Set the value of the attribute to the component to show when a user clicks on each link. Next, update your component template to include ``. This element informs Angular to update the application view with the component for the selected route. -```html +```angular-html

Angular Router App