diff --git a/.editorconfig b/.editorconfig
index cdc2ca9..7a9373a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,6 +1,8 @@
root = true
-[*]
+[*.{kt,kts}]
indent_size = 2
-continuation_indent_size = 4
-ij_kotlin_name_count_to_use_star_import = unset
+indent_style = space
+ignored_rules = no-wildstar-imports
+insert_final_newline = false
+max_line_length = 100
diff --git a/.fleet/run.json b/.fleet/run.json
new file mode 100644
index 0000000..56660b7
--- /dev/null
+++ b/.fleet/run.json
@@ -0,0 +1,13 @@
+{
+ "configurations": [
+ {
+ "type": "gradle",
+ "name": "Run CLI",
+ "environment": {}, // optional
+ "dependsOn": [], // optional
+ "tasks": [":app:cli:jvmRun"],
+ "args": ["--args='-h'"], // optional
+ "initScripts": {}, // optional
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..7e06640
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @mpetuska
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e3c0f99..212a26f 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,14 +1,14 @@
version: 2
updates:
- package-ecosystem: "github-actions"
- directory: "/"
+ directory: "/.github/workflows"
schedule:
- interval: "daily"
+ interval: "monthly"
- package-ecosystem: "gradle"
directory: "/"
schedule:
- interval: "daily"
+ interval: "monthly"
- package-ecosystem: "terraform"
directory: "/infra"
schedule:
- interval: "daily"
+ interval: "monthly"
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
new file mode 100644
index 0000000..387e338
--- /dev/null
+++ b/.github/workflows/check.yml
@@ -0,0 +1,68 @@
+name: Check
+defaults:
+ run:
+ shell: bash
+
+on:
+ push:
+ branches:
+ - master
+ workflow_dispatch:
+ workflow_call:
+
+jobs:
+ check:
+ name: Check on ubuntu-latest
+ runs-on: ubuntu-latest
+ env:
+ GRADLE_OPTS: "-Dorg.gradle.daemon=true"
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-java@v3
+ with:
+ distribution: 'adopt'
+ java-version: 11
+
+ - name: Restore Gradle cache
+ id: cache
+ uses: actions/cache@v3.0.2
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ ~/.gradle/yarn
+ ~/.gradle/nodejs
+ ~/.konan
+ ~/.cache/yarn
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+ restore-keys: ${{ runner.os }}-gradle-
+
+ - name: Gradle Check
+ run: ./gradlew detektAll check -x detekt --scan
+
+ - name: Make artifact location URIs relative
+ if: ${{ always() }}
+ continue-on-error: true
+ run: |
+ cp ${{ github.workspace }}/build/reports/detekt/detekt.sarif ${{ github.workspace }}/detekt.sarif.json
+ echo "$(
+ jq \
+ --arg github_workspace ${{ github.workspace }} \
+ '. | ( .runs[].results[].locations[].physicalLocation.artifactLocation.uri |= if test($github_workspace) then .[($github_workspace | length | . + 1):] else . end )' \
+ ${{ github.workspace }}/detekt.sarif.json
+ )" > ${{ github.workspace }}/detekt.sarif.json
+
+ - uses: github/codeql-action/upload-sarif@v2
+ if: ${{ always() }}
+ with:
+ sarif_file: ${{ github.workspace }}/detekt.sarif.json
+ checkout_path: ${{ github.workspace }}
+
+ - uses: actions/upload-artifact@v3
+ if: ${{ always() }}
+ with:
+ name: reports-${{ runner.os }}
+ path: |
+ **/build/reports
+ detekt.sarif.json
diff --git a/.github/workflows/detekt-analysis.yml b/.github/workflows/detekt-analysis.yml
deleted file mode 100644
index 281332d..0000000
--- a/.github/workflows/detekt-analysis.yml
+++ /dev/null
@@ -1,103 +0,0 @@
-# This workflow performs a static analysis of your Kotlin source code using
-# Detekt.
-#
-# Scans are triggered:
-# 1. On every push to default and protected branches
-# 2. On every Pull Request targeting the default branch
-# 3. On a weekly schedule
-# 4. Manually, on demand, via the "workflow_dispatch" event
-#
-# The workflow should work with no modifications, but you might like to use a
-# later version of the Detekt CLI by modifing the $DETEKT_RELEASE_TAG
-# environment variable.
-name: Scan with Detekt
-
-on:
- # Triggers the workflow on push or pull request events but only for default and protected branches
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
- schedule:
- - cron: '26 18 * * 0'
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-env:
- # Release tag associated with version of Detekt to be installed
- # SARIF support (required for this workflow) was introduced in Detekt v1.15.0
- DETEKT_RELEASE_TAG: v1.15.0
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "scan"
- scan:
- name: Scan
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
-
- # Gets the download URL associated with the $DETEKT_RELEASE_TAG
- - name: Get Detekt download URL
- id: detekt_info
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- DETEKT_DOWNLOAD_URL=$( gh api graphql --field tagName=$DETEKT_RELEASE_TAG --raw-field query='
- query getReleaseAssetDownloadUrl($tagName: String!) {
- repository(name: "detekt", owner: "detekt") {
- release(tagName: $tagName) {
- releaseAssets(name: "detekt", first: 1) {
- nodes {
- downloadUrl
- }
- }
- }
- }
- }
- ' | \
- jq --raw-output '.data.repository.release.releaseAssets.nodes[0].downloadUrl' )
- echo "::set-output name=download_url::$DETEKT_DOWNLOAD_URL"
-
- # Sets up the detekt cli
- - name: Setup Detekt
- run: |
- dest=$( mktemp -d )
- curl --request GET \
- --url ${{ steps.detekt_info.outputs.download_url }} \
- --silent \
- --location \
- --output $dest/detekt
- chmod a+x $dest/detekt
- echo $dest >> $GITHUB_PATH
-
- # Performs static analysis using Detekt
- - name: Run Detekt
- continue-on-error: true
- run: |
- detekt --input ${{ github.workspace }} --report sarif:${{ github.workspace }}/detekt.sarif.json
-
- # Modifies the SARIF output produced by Detekt so that absolute URIs are relative
- # This is so we can easily map results onto their source files
- # This can be removed once relative URI support lands in Detekt: https://git.io/JLBbA
- - name: Make artifact location URIs relative
- continue-on-error: true
- run: |
- echo "$(
- jq \
- --arg github_workspace ${{ github.workspace }} \
- '. | ( .runs[].results[].locations[].physicalLocation.artifactLocation.uri |= if test($github_workspace) then .[($github_workspace | length | . + 1):] else . end )' \
- ${{ github.workspace }}/detekt.sarif.json
- )" > ${{ github.workspace }}/detekt.sarif.json
-
- # Uploads results to GitHub repository using the upload-sarif action
- - uses: github/codeql-action/upload-sarif@v1
- with:
- # Path to SARIF file relative to the root of the repository
- sarif_file: ${{ github.workspace }}/detekt.sarif.json
- checkout_path: ${{ github.workspace }}
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 826baaf..1784706 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -2,12 +2,20 @@ name: PR CI/CD
on:
pull_request:
- types: [opened, synchronize, reopened, closed]
+ types: [ opened, synchronize, reopened, closed ]
branches:
- master
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
jobs:
+ check:
+ uses: ./.github/workflows/check.yml
+
build:
+ needs: [ check ]
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
name: Build App
runs-on: ubuntu-latest
@@ -36,7 +44,7 @@ jobs:
path: app/client/build/dist/js/WEB-INF/
name: static-web-app
if-no-files-found: error
-
+
deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
name: Deploy Staging
@@ -53,7 +61,7 @@ jobs:
with:
path: dist/application.env
contents: |
- API_URL=https://kamp.azurewebsites.net
+ API_URL=https://kodex.azurewebsites.net
write-mode: overwrite
- name: Deploy Azure Static Web App
uses: Azure/static-web-apps-deploy@v0.0.1-preview
diff --git a/.github/workflows/release-kodex.yml b/.github/workflows/release-kodex.yml
index 594cd81..3ab1561 100644
--- a/.github/workflows/release-kodex.yml
+++ b/.github/workflows/release-kodex.yml
@@ -2,11 +2,22 @@ name: Release Kodex
on:
workflow_dispatch:
+ push:
+ branches:
+ - kodex
+
+concurrency:
+ group: kodex
+ cancel-in-progress: false
jobs:
+ # check:
+ # uses: ./.github/workflows/check.yml
+
build:
name: Build Apps
runs-on: ubuntu-latest
+ environment: kodex
env:
REGISTRY: ghcr.io
steps:
@@ -51,21 +62,25 @@ jobs:
# username: ${{ github.actor }}
# password: ${{ github.token }}
# registry: docker.pkg.github.com
- # repository: mpetuska/kamp/app
+ # repository: mpetuska/kodex/app
# tag_with_ref: true
# tag_with_sha: true
# add_git_labels: true
# tags: latest
- - name: Build CLI Docker image & push to GitHub Packages
- uses: docker/build-push-action@v5
+ - name: Login to DockerHub
+ uses: docker/login-action@v2
with:
- directory: ./app/cli/
+ registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- registry: ${{ env.REGISTRY }}
- image: ${{ env.REGISTRY }}/${{ github.repository }}/kodex/cli
- addLatest: true
+
+ - name: Build CLI Docker image & push to GitHub Packages
+ uses: docker/build-push-action@v3
+ with:
+ context: ./app/cli/
+ push: true
+ tags: ${{ env.REGISTRY }}/${{ github.repository }}/kodex/cli:latest
# deploy-Infrastructure:
# runs-on: ubuntu-latest
@@ -101,7 +116,7 @@ jobs:
# env:
# TF_VAR_docker_registry_username: ${{ secrets.GH_PKG_USER }}
# TF_VAR_docker_registry_password: ${{ secrets.GH_PKG_PASSWORD }}
-# TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kamp/app:sha-${{ steps.short-sha.outputs.sha }}
+# TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kodex/app:sha-${{ steps.short-sha.outputs.sha }}
# TF_VAR_api_admin_user: ${{ secrets.API_ADMIN_USER }}
# TF_VAR_api_admin_password: ${{ secrets.API_ADMIN_PASSWORD }}
#
@@ -120,7 +135,7 @@ jobs:
# with:
# path: dist/application.env
# contents: |
-# API_URL=https://kamp.azurewebsites.net
+# API_URL=https://kodex.azurewebsites.net
# write-mode: overwrite
# - name: Deploy Azure Static Web App
# uses: Azure/static-web-apps-deploy@v0.0.1-preview
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c775901..0fd9e2f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,9 +6,13 @@ on:
types: [ created ]
jobs:
+ check:
+ uses: ./.github/workflows/check.yml
+
build:
name: Build App
runs-on: ubuntu-latest
+ needs: [ check ]
steps:
- uses: actions/checkout@v2.3.4
- name: Restore Gradle cache
@@ -32,29 +36,29 @@ jobs:
uses: actions/upload-artifact@v2
id: upload
with:
- path: app/build/dist/WEB-INF
+ path: app/client/build/dist/js/WEB-INF/
name: static-web-app
if-no-files-found: error
- - name: Build App Docker image & push to GitHub Packages
+ - name: Build Server Docker image & push to GitHub Packages
uses: docker/build-push-action@v1
with:
- path: app
+ path: app/server/
username: ${{ github.actor }}
password: ${{ github.token }}
registry: docker.pkg.github.com
- repository: mpetuska/kamp/app
+ repository: mpetuska/kodex/server
tag_with_ref: true
tag_with_sha: true
add_git_labels: true
tags: latest
- - name: Build Scanner Docker image & push to GitHub Packages
+ - name: Build CLI Docker image & push to GitHub Packages
uses: docker/build-push-action@v1
with:
- path: scanner
+ path: app/cli/
username: ${{ github.actor }}
password: ${{ github.token }}
registry: docker.pkg.github.com
- repository: mpetuska/kamp/scanner
+ repository: mpetuska/kodex/cli
tag_with_ref: true
tag_with_sha: true
add_git_labels: true
@@ -94,7 +98,7 @@ jobs:
env:
TF_VAR_docker_registry_username: ${{ secrets.GH_PKG_USER }}
TF_VAR_docker_registry_password: ${{ secrets.GH_PKG_PASSWORD }}
- TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kamp/app:sha-${{ steps.short-sha.outputs.sha }}
+ TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kodex/server:sha-${{ steps.short-sha.outputs.sha }}
TF_VAR_api_admin_user: ${{ secrets.API_ADMIN_USER }}
TF_VAR_api_admin_password: ${{ secrets.API_ADMIN_PASSWORD }}
@@ -113,12 +117,12 @@ jobs:
with:
path: dist/application.env
contents: |
- API_URL=https://kamp.azurewebsites.net
+ API_URL=https://kodex.azurewebsites.net
write-mode: overwrite
- name: Deploy Azure Static Web App
uses: Azure/static-web-apps-deploy@v0.0.1-preview
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APP_API_TOKEN }}
- repo_token: ${{ github.token }} # Used for Github integrations (i.e. PR comments)
+ repo_token: ${{ github.token }}
action: "upload"
app_location: "/dist"
diff --git a/.github/workflows/scan-kodex.yml b/.github/workflows/scan-kodex.yml
index 716c0f1..92aaef6 100644
--- a/.github/workflows/scan-kodex.yml
+++ b/.github/workflows/scan-kodex.yml
@@ -13,13 +13,10 @@ on:
description: 'CLI image tag'
required: true
default: latest
- schedule:
- - cron: '0 0 * * *'
+# schedule:
+# - cron: '0 0 * * *'
env:
- API_URL: https://kamp.azurewebsites.net
- ADMIN_USER: ${{ secrets.API_ADMIN_USER }}
- ADMIN_PASSWORD: ${{ secrets.API_ADMIN_PASSWORD }}
MONGO_STRING: ${{ secrets.MONGO_STRING }}
MONGO_DATABASE: ${{ secrets.MONGO_DATABASE }}
REGISTRY: ghcr.io
@@ -27,45 +24,97 @@ env:
TAG: ${{ github.event.inputs.tag }}
LOG_LEVEL: ${{ github.event.inputs.logLevel }}
-concurrency:
- group: production
- cancel-in-progress: false
-
permissions:
- packages: write
- id-token: write
+ packages: read
jobs:
scan:
runs-on: ubuntu-latest
environment: kodex
strategy:
- max-parallel: 10
+ max-parallel: 50
matrix:
repository:
- 'mavenCentral'
options:
- - '--include=com'
- - '--include=org'
- - '--include=io'
- - '--from=a --to=c --exclude=com'
- - '--from=d --to=f'
- - '--from=g --to=l --exclude=io'
- - '--from=m --to=p --exclude=org'
- - '--from=q --to=t'
- - '--from=u --to=z'
+ # com/
+ - '--root=com/ --exclude-letters'
+ - '--root=com/ --include=github/'
+ - '--root=com/ --include=google/'
+ - '--root=com/ --from=a --to=h --exclude=github/ --exclude=google/'
+ - '--root=com/ --from=i --to=q'
+ - '--root=com/ --from=q --to=z'
+ # org/
+ - '--root=org/ --exclude-letters'
+ - '--root=org/ --include=webjars/'
+ - '--root=org/ --include=netbeans/'
+ - '--root=org/ --from=a --to=h'
+ - '--root=org/ --from=i --to=q --exclude=netbeans/'
+ - '--root=org/ --from=q --to=z --exclude=webjars/'
+ # io/
+ - '--root=io/ --exclude-letters'
+ - '--root=io/ --include=github/'
+ - '--root=io/ --from=a --to=h --exclude=github/'
+ - '--root=io/ --from=i --to=q'
+ - '--root=io/ --from=q --to=z'
+ # Rest
+ - '--exclude-letters'
+ - '--include=a'
+ - '--include=b'
+ - '--include=c --exclude=com/'
+ - '--include=d'
+ - '--include=e'
+ - '--include=f'
+ - '--include=g'
+ - '--include=h'
+ - '--include=i --exclude=io/'
+ - '--include=j'
+ - '--include=k'
+ - '--include=l'
+ - '--include=m'
+ - '--include=n'
+ - '--include=o --exclude=org/'
+ - '--include=p'
+ - '--include=q'
+ - '--include=r'
+ - '--include=s'
+ - '--include=t'
+ - '--include=u'
+ - '--include=v'
+ - '--include=w'
+ - '--include=x'
+ - '--include=y'
+ - '--include=z'
steps:
- - uses: docker/login-action@v2.0.0
+ - name: Login to ${{ env.REGISTRY }}
+ uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- - run: |
+ - name: Scan ${{ matrix.repository }} ${{ matrix.options }}
+ run: |
docker run -a stderr -a stdout \
-e="LOG_LEVEL=${LOG_LEVEL:-INFO}" \
- -e=API_URL \
- -e=ADMIN_USER \
- -e=ADMIN_PASSWORD \
- -e=MONGO_STRING \
- -e=MONGO_DATABASE \
+ -e="MONGO_STRING" \
+ -e="MONGO_DATABASE" \
"${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG:-latest}" scan ${{ matrix.repository }} ${{ matrix.options }}
+
+ capture:
+ needs: [ scan ]
+ runs-on: ubuntu-latest
+ environment: kodex
+ steps:
+ - name: Login to ${{ env.REGISTRY }}
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ github.token }}
+ - name: Capture the statistics
+ run: |
+ docker run -a=stderr -a=stdout --pull=always \
+ -e="LOG_LEVEL=${LOG_LEVEL:-INFO}" \
+ -e="MONGO_STRING" \
+ -e="MONGO_DATABASE" \
+ "${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG:-latest}" capture
diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml
index e3d7211..4adde20 100644
--- a/.github/workflows/scan.yml
+++ b/.github/workflows/scan.yml
@@ -11,10 +11,10 @@ on:
- cron: '0 0 * * *'
env:
- API_URL: https://kamp.azurewebsites.net
+ API_URL: https://kodex.azurewebsites.net
ADMIN_USER: ${{ secrets.API_ADMIN_USER }}
ADMIN_PASSWORD: ${{ secrets.API_ADMIN_PASSWORD }}
- IMAGE: docker.pkg.github.com/mpetuska/kamp/scanner:latest
+ IMAGE: docker.pkg.github.com/mpetuska/kodex/cli
LOG_LEVEL: ${{ github.event.inputs.logLevel }}
jobs:
@@ -40,4 +40,8 @@ jobs:
registry: docker.pkg.github.com
username: ${{ github.actor }}
password: ${{ github.token }}
- - run: docker run -a stderr -a stdout -e "LOG_LEVEL=${LOG_LEVEL:-INFO}" -e API_URL -e ADMIN_USER -e ADMIN_PASSWORD ${{ env.IMAGE }} ${{ matrix.repository }} ${{ matrix.options }}
+ - uses: benjlevesque/short-sha@v1.2
+ id: short-sha
+ with:
+ length: 7
+ - run: docker run -a stderr -a stdout -e "LOG_LEVEL=${LOG_LEVEL:-INFO}" -e API_URL -e ADMIN_USER -e ADMIN_PASSWORD ${{ env.IMAGE }}:sha-${{ steps.short-sha.outputs.sha }} ${{ matrix.repository }} ${{ matrix.options }}
diff --git a/.github/workflows/tf-refresh.yml b/.github/workflows/tf-refresh.yml
index 39f0343..bdd1f4a 100644
--- a/.github/workflows/tf-refresh.yml
+++ b/.github/workflows/tf-refresh.yml
@@ -39,4 +39,4 @@ jobs:
env:
TF_VAR_docker_registry_username: ${{ secrets.GH_PKG_USER }}
TF_VAR_docker_registry_password: ${{ secrets.GH_PKG_PASSWORD }}
- TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kamp/app:sha-${{ steps.short-sha.outputs.sha }}
+ TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kodex/app:sha-${{ steps.short-sha.outputs.sha }}
diff --git a/.gitignore b/.gitignore
index 6bdca35..969b598 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# Ignore Gradle project-specific cache directory
.gradle/
.idea/
+!.idea/icon.png
local.properties
*.log
logs/
@@ -11,5 +12,6 @@ libs/
build
.terraform/
*.tfvars
+nohup.out
graalvm-ce-*.tar.gz
app/client/externals
diff --git a/.idea/icon.png b/.idea/icon.png
new file mode 120000
index 0000000..922303c
--- /dev/null
+++ b/.idea/icon.png
@@ -0,0 +1 @@
+../docs/kamp.png
\ No newline at end of file
diff --git a/.run/Run All.run.xml b/.run/Run All.run.xml
new file mode 100644
index 0000000..945181b
--- /dev/null
+++ b/.run/Run All.run.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/Run Mongo.run.xml b/.run/Run Mongo.run.xml
new file mode 100644
index 0000000..a905c3a
--- /dev/null
+++ b/.run/Run Mongo.run.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e69de29..6e01113 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+# WIP
+## Changes
+* Compose!
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 3ad6fa6..0b8aa4a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -136,7 +136,7 @@
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
+ names, trademarks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
diff --git a/README.md b/README.md
index e03e738..2b2e4b4 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,36 @@
-[![Gitpod ready-to-code](https://img.shields.io/badge/gitpod-ready--to--code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/mpetuska/kamp)
+[![Gitpod ready-to-code](https://img.shields.io/badge/gitpod-ready--to--code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/mpetuska/kodex)
[![Kotlin JS IR supported](https://img.shields.io/badge/Kotlin%2FJS-IR%20supported-yellow?style=flat-square&logo=kotlin)](https://kotl.in/jsirsupported)
-# [KAMP](https://www.kamp.ml)
+# [KODEX](https://www.kodex.ml)
-This project aims to provide an extensive catalogue of kotlin multiplatform projects across various maven repositories
+This project aims to provide an extensive catalogue of kotlin multiplatform projects across various
+maven repositories
out there.
## Motivation
The project is heavily inspired by existing community catalogues such
as [Awesome Kotlin](https://github.com/KotlinBy/awesome-kotlin)
-& [multiplatform-libraries](https://github.com/icerockdev/multiplatform-libraries). While they all provide the same
-feature as this project (a catalogue for kotlin libraries), they all require the library authors to be aware of them and
-register their creations. This project aims to automate that to ensure the community has an extensive self-updating
+& [multiplatform-libraries](https://github.com/icerockdev/multiplatform-libraries). While they all
+provide the same
+feature as this project (a catalogue for kotlin libraries), they all require the library authors to
+be aware of them and
+register their creations. This project aims to automate that to ensure the community has an
+extensive self-updating
catalogue of all the marvelous kotlin creations out there!
## Structure
-The project consists of two modules - a background processor providing the data feed and a webapp for exposing it as a
+The project consists of two modules - a background processor providing the data feed and a webapp
+for exposing it as a
catalogue.
### Scanner
-An internet crawler that's meant to scan and identify kotlin libraries across the web. It is only able to find the
-libraries published with gradle-metadata, so older Kotlin libraries might be missing. Currently, supported maven
+An internet crawler that's meant to scan and identify kotlin libraries across the web. It is only
+able to find the
+libraries published with gradle-metadata, so older Kotlin libraries might be missing. Currently,
+supported maven
repositories are:
* [Maven Central](https://repo1.maven.org/maven2)
@@ -35,4 +42,4 @@ repositories are:
## App
-The driving server and client to expose the data collected by the scanner as a nice web-based catalogue.
+The driving server and client to expose the data collected by the cli as a nice web-based catalogue.
diff --git a/app/app-cli/Dockerfile b/app/app-cli/Dockerfile
new file mode 100644
index 0000000..e9f4126
--- /dev/null
+++ b/app/app-cli/Dockerfile
@@ -0,0 +1,6 @@
+FROM gcr.io/distroless/java:11
+
+WORKDIR app
+COPY build/libs/cli-jvm-*-fat.jar cli.jar
+
+ENTRYPOINT ["java", "-jar", "cli.jar"]
diff --git a/app/app-cli/build.gradle.kts b/app/app-cli/build.gradle.kts
new file mode 100644
index 0000000..fb60529
--- /dev/null
+++ b/app/app-cli/build.gradle.kts
@@ -0,0 +1,43 @@
+plugins {
+ id("convention.app-jvm")
+ alias(libs.plugins.kotlin.serialization)
+}
+
+app {
+ jvm {
+ mainClass.set("dev.petuska.kodex.cli.MainKt")
+ }
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ implementation(projects.lib.libCore)
+ implementation(projects.lib.libRepository)
+
+ implementation(libs.ktor.client.core)
+ implementation(libs.ktor.client.auth)
+ implementation(libs.ktor.client.content.negotiation)
+ implementation(libs.ktor.serialization.kotlinx.json)
+ implementation(libs.ktor.serialization.kotlinx.cbor)
+
+ implementation(libs.clikt)
+ implementation(libs.koin.core)
+ implementation(libs.koin.logger.slf4j)
+ }
+ }
+ commonTest {
+ dependencies {
+ implementation(projects.lib.libTest)
+ }
+ }
+ jvmMain {
+ dependencies {
+ implementation(libs.ktor.client.cio)
+ implementation(libs.jsoup)
+ implementation(libs.logback.classic)
+ }
+ }
+ }
+}
diff --git a/scanner/docker-compose.yml b/app/app-cli/docker-compose.yml
similarity index 54%
rename from scanner/docker-compose.yml
rename to app/app-cli/docker-compose.yml
index 30f787a..14bd658 100644
--- a/scanner/docker-compose.yml
+++ b/app/app-cli/docker-compose.yml
@@ -5,15 +5,6 @@ services:
ports:
- "27017:27017"
- # mongo-seed:
- # build:
- # context: .
- # dockerfile: db.dockerfile
- # args:
- # input: out
- # depends_on:
- # - mongo
-
mongo-express:
image: mongo-express
ports:
diff --git a/app/app-cli/importData.main.kts b/app/app-cli/importData.main.kts
new file mode 100755
index 0000000..f41763c
--- /dev/null
+++ b/app/app-cli/importData.main.kts
@@ -0,0 +1,36 @@
+#!/usr/bin/env kotlin
+
+@file:DependsOn("io.projectreactor:reactor-core:3.4.11")
+@file:DependsOn("org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3")
+@file:DependsOn("org.litote.kmongo:kmongo-coroutine-serialization:4.3.0")
+
+import com.mongodb.client.model.*
+import kotlinx.cli.*
+import kotlinx.coroutines.runBlocking
+import org.bson.Document
+import org.litote.kmongo.*
+import org.litote.kmongo.coroutine.*
+import org.litote.kmongo.reactivestreams.*
+
+runBlocking {
+ val parser = ArgParser("importData")
+ val sourceUrl by parser.argument(ArgType.String, description = "Source mongo url")
+ val targetUrl by parser.argument(
+ ArgType.String,
+ description = "Target mongo url"
+ ) // .default("mongodb://localhost:27017")
+ parser.parse(args)
+
+ val sourceDB = KMongo.createClient(sourceUrl).coroutine
+ val targetDB = KMongo.createClient(targetUrl).coroutine
+ val targetCol = targetDB.getDatabase("kodex").getCollection("libraries")
+ sourceDB.getDatabase("kodex").getCollection("libraries").find().consumeEach {
+ val id = it["_id"]?.also {
+ targetCol.deleteOneById(it)
+ }
+ println("Inserting $id")
+ targetCol.insertOne(it)
+ }
+ sourceDB.close()
+ targetDB.close()
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/KodexCmd.kt b/app/app-cli/src/jvmMain/kotlin/cmd/KodexCmd.kt
new file mode 100644
index 0000000..168d64f
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/KodexCmd.kt
@@ -0,0 +1,19 @@
+package dev.petuska.kodex.cli.cmd
+
+import com.github.ajalt.clikt.completion.CompletionCommand
+import com.github.ajalt.clikt.core.NoOpCliktCommand
+import com.github.ajalt.clikt.core.subcommands
+import dev.petuska.kodex.cli.cmd.capture.CaptureCmd
+import dev.petuska.kodex.cli.cmd.scan.ScanCmd
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.get
+
+class KodexCmd : NoOpCliktCommand(name = "kodex"), KoinComponent {
+ init {
+ subcommands(
+ CompletionCommand(),
+ get(),
+ get(),
+ )
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/capture/CaptureCmd.kt b/app/app-cli/src/jvmMain/kotlin/cmd/capture/CaptureCmd.kt
new file mode 100644
index 0000000..9ac77ad
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/capture/CaptureCmd.kt
@@ -0,0 +1,30 @@
+package dev.petuska.kodex.cli.cmd.capture
+
+import com.github.ajalt.clikt.core.CliktCommand
+import dev.petuska.kodex.cli.util.LoggerDelegate
+import dev.petuska.kodex.repository.LibraryRepository
+import dev.petuska.kodex.repository.StatisticRepository
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import org.koin.core.qualifier.named
+
+class CaptureCmd :
+ CliktCommand(name = "capture", help = "Capture a snapshot of current library distribution"),
+ KoinComponent {
+
+ private val libRepository: LibraryRepository by inject()
+ private val statRepository: StatisticRepository by inject()
+ private val logger by LoggerDelegate()
+ private val json by inject(named("pretty"))
+
+ override fun run() = runBlocking {
+ logger.info("Extracting statistics...")
+ val stats = libRepository.captureStatistics()
+ logger.info("Persisting statistics...")
+ statRepository.create(stats)
+ logger.info("Done!\n" + json.encodeToString(stats))
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/ScanCmd.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/ScanCmd.kt
new file mode 100644
index 0000000..27dfbb2
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/ScanCmd.kt
@@ -0,0 +1,129 @@
+package dev.petuska.kodex.cli.cmd.scan
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.groups.OptionGroup
+import com.github.ajalt.clikt.parameters.groups.cooccurring
+import com.github.ajalt.clikt.parameters.options.*
+import com.github.ajalt.clikt.parameters.types.choice
+import com.github.ajalt.clikt.parameters.types.long
+import dev.petuska.kodex.cli.cmd.scan.domain.FileData
+import dev.petuska.kodex.cli.cmd.scan.domain.Repository
+import dev.petuska.kodex.cli.cmd.scan.domain.SimpleMavenArtefact
+import dev.petuska.kodex.cli.cmd.scan.service.PageService
+import dev.petuska.kodex.cli.cmd.scan.service.SimpleMavenArtefactService
+import dev.petuska.kodex.cli.util.LoggerDelegate
+import dev.petuska.kodex.cli.util.toHumanString
+import dev.petuska.kodex.core.domain.KotlinLibrary
+import dev.petuska.kodex.core.domain.KotlinTarget
+import dev.petuska.kodex.repository.LibraryRepository
+import io.ktor.client.*
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import org.koin.core.qualifier.named
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.ExperimentalTime
+import kotlin.time.measureTime
+
+class ScanCmd :
+ CliktCommand(name = "scan", help = "Scan a maven repository for kotlin libraries"),
+ KoinComponent {
+ private class FilterOptions : OptionGroup() {
+ val from by option(help = "Repository root page filter start").choice(
+ ('a'..'z').associateBy(Char::toString),
+ ignoreCase = true
+ ).required()
+
+ val to by option(help = "Repository root page filter end").choice(
+ ('a'..'z').associateBy(Char::toString),
+ ignoreCase = true
+ ).required()
+ }
+
+ private val repository by argument(help = "Repository alias to scan for").choice(
+ Repository.values().associateBy(Repository::alias),
+ ignoreCase = true
+ )
+
+ private val root by option(help = "Repository root page path").default("/")
+
+ private val include by option(help = "Repository root page filter to include").multiple()
+
+ private val exclude by option(help = "Repository root page filter to exclude").multiple()
+
+ private val excludeLetters by option(help = "Same as if you were to pass --exclude for a..z")
+ .flag()
+
+ private val delay by option(help = "Delay between subpage scans in milliseconds").long()
+ .convert { it.milliseconds }
+ .default(10.milliseconds)
+
+ private val filterOptions by FilterOptions().cooccurring()
+
+ // =============================================================================================
+ private val libraryRepository: LibraryRepository by inject()
+
+ private val logger by LoggerDelegate()
+
+ override fun run() = runBlocking {
+ val client = run {
+ val json by inject(named("pretty"))
+ val httpClient by inject()
+ repository.client(repository.url, httpClient, json)
+ }
+ logger.info("Scanning ${repository.alias} repository from $root")
+
+ val includes = include.plus(
+ filterOptions?.run { from..to }?.map(Char::toString) ?: listOf()
+ )
+ val excludes = exclude.plus(
+ if (excludeLetters) ('a'..'z').map(Char::toString) else listOf()
+ )
+
+ logger.info("Bootstrapping repository page lookup")
+ var pageCount = 0
+ val pages = PageService(
+ client = client,
+ delay = delay,
+ ).findPages(include = includes, exclude = excludes, path = root).buffer()
+ .onEach { pageCount++ }
+ logger.info("Bootstrapping maven artefact lookup")
+ val scanner = SimpleMavenArtefactService(
+ client = client,
+ )
+ var libCount = 0
+
+ @OptIn(ExperimentalTime::class)
+ val duration = measureTime {
+ coroutineScope {
+ val artefacts: Flow> =
+ scanner.findMavenArtefacts(pages).buffer()
+ val libraries: Flow> =
+ scanner.findKotlinLibraries(artefacts).buffer()
+ libraries.collect { (_, lib) ->
+ logger.info("Found kotlin library: ${lib._id} ${lib.targets.map(KotlinTarget::id)}")
+ libCount++
+ launch { libraryRepository.create(lib) }
+ }
+ client.close()
+ }
+ }
+ val filterMsg =
+ " scanning from $root filtered by $includes, explicitly excluding $excludes."
+ logger.info(
+ "Finished scanning ${repository.alias} in ${duration.toHumanString()} " +
+ "and scanned $pageCount subpages" + filterMsg
+ )
+ logger.info(
+ "Found $libCount kotlin libraries with gradle metadata in ${repository.alias} repository" +
+ filterMsg
+ )
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/AnchorClient.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/AnchorClient.kt
new file mode 100644
index 0000000..c13eefc
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/AnchorClient.kt
@@ -0,0 +1,16 @@
+package dev.petuska.kodex.cli.cmd.scan.client
+
+import dev.petuska.kodex.core.domain.MavenArtefact
+import org.jsoup.nodes.Document
+
+abstract class AnchorClient(
+ val url: String,
+) : MavenRepositoryClient(url) {
+ abstract fun String.isBackLink(): Boolean
+
+ override fun parsePage(page: Document): List? =
+ page.getElementsByTag("a").mapNotNull { elm ->
+ val text = elm.attr("href")
+ text.takeIf { it.isNotBlank() && !elm.text().isBackLink() }
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/ArtifactoryClient.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/ArtifactoryClient.kt
new file mode 100644
index 0000000..293c236
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/ArtifactoryClient.kt
@@ -0,0 +1,28 @@
+package dev.petuska.kodex.cli.cmd.scan.client
+
+import dev.petuska.kodex.cli.cmd.scan.domain.SimpleMavenArtefact
+import io.ktor.client.HttpClient
+import kotlinx.serialization.json.Json
+
+class ArtifactoryClient(
+ url: String,
+ override val client: HttpClient,
+ override val json: Json,
+) : AnchorClient(url) {
+ override fun String.isBackLink(): Boolean = startsWith("..")
+ override fun buildArtefact(
+ group: String,
+ name: String,
+ latestVersion: String,
+ releaseVersion: String?,
+ versions: List?,
+ lastUpdated: Long?
+ ) = SimpleMavenArtefact(
+ group = group,
+ name = name,
+ latestVersion = latestVersion,
+ releaseVersion = releaseVersion,
+ versions = versions,
+ lastUpdated = lastUpdated,
+ )
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/JBossClient.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/JBossClient.kt
new file mode 100644
index 0000000..788df0e
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/JBossClient.kt
@@ -0,0 +1,28 @@
+package dev.petuska.kodex.cli.cmd.scan.client
+
+import dev.petuska.kodex.cli.cmd.scan.domain.SimpleMavenArtefact
+import io.ktor.client.HttpClient
+import kotlinx.serialization.json.Json
+
+class JBossClient(
+ url: String,
+ override val client: HttpClient,
+ override val json: Json,
+) : AnchorClient(url) {
+ override fun String.isBackLink(): Boolean = equals("Parent Directory", true)
+ override fun buildArtefact(
+ group: String,
+ name: String,
+ latestVersion: String,
+ releaseVersion: String?,
+ versions: List?,
+ lastUpdated: Long?
+ ) = SimpleMavenArtefact(
+ group = group,
+ name = name,
+ latestVersion = latestVersion,
+ releaseVersion = releaseVersion,
+ versions = versions,
+ lastUpdated = lastUpdated,
+ )
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/MavenRepositoryClient.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/MavenRepositoryClient.kt
new file mode 100644
index 0000000..58337b1
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/client/MavenRepositoryClient.kt
@@ -0,0 +1,134 @@
+package dev.petuska.kodex.cli.cmd.scan.client
+
+import dev.petuska.kodex.cli.cmd.scan.domain.*
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoItem.Companion.SEP
+import dev.petuska.kodex.cli.cmd.scan.util.gradleMetadataFile
+import dev.petuska.kodex.cli.cmd.scan.util.mavenPomFile
+import dev.petuska.kodex.cli.util.LoggerDelegate
+import dev.petuska.kodex.cli.util.asDocument
+import dev.petuska.kodex.core.domain.MavenArtefact
+import dev.petuska.kodex.repository.util.runCatchingIO
+import io.ktor.client.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import io.ktor.util.date.*
+import io.ktor.utils.io.core.*
+import kotlinx.coroutines.supervisorScope
+import kotlinx.serialization.json.Json
+import org.jsoup.nodes.Document
+
+abstract class MavenRepositoryClient(
+ private val repositoryRootUrl: String,
+) : Closeable {
+ protected abstract fun parsePage(page: Document): List?
+
+ @Suppress("LongParameterList")
+ protected abstract fun buildArtefact(
+ group: String,
+ name: String,
+ latestVersion: String,
+ releaseVersion: String?,
+ versions: List?,
+ lastUpdated: Long?
+ ): A
+
+ protected abstract val client: HttpClient
+ protected abstract val json: Json
+ private val logger by LoggerDelegate()
+
+ suspend fun getMavenArtefact(mavenMetadata: RepoFile): FileData? = supervisorScope {
+ val pom = getMavenPom(mavenMetadata)
+ val doc = pom?.data?.getElementsByTag("metadata")?.first()
+ doc?.let {
+ runCatching {
+ val versions = doc.selectFirst("versioning>versions")?.children()?.map { v -> v.text() }
+ versions?.let {
+ val lastUpdated = doc.selectFirst("versioning>lastUpdated")?.text()?.let {
+ GMTDate(
+ year = it.substring(0 until 4).toInt(),
+ month = Month.from(it.substring(4 until 6).toInt() - 1),
+ dayOfMonth = it.substring(6 until 8).toInt(),
+ hours = it.substring(8 until 10).toInt(),
+ minutes = it.substring(10 until 12).toInt(),
+ seconds = it.substring(12 until 14).toInt(),
+ ).timestamp
+ }
+ val latestVersion = doc.selectFirst("versioning>latest")?.text()
+ ?: doc.selectXpath("//version").first()?.text()
+ ?: versions.last()
+ buildArtefact(
+ // https://repo1.maven.org/maven2/com/inmobi/monetization/inmobi-mediation/maven-metadata.xml
+ group = doc.selectFirst("groupId")?.text() ?: doc.selectFirst("groupdId")!!.text(),
+ name = doc.selectFirst("artifactId")!!.text(),
+ latestVersion = latestVersion,
+ releaseVersion = doc.selectFirst("versioning>release")?.text(),
+ versions = versions,
+ lastUpdated = lastUpdated,
+ ).let(pom.file::data)
+ }
+ }.onFailure {
+ if (doc.selectFirst("plugins") == null) {
+ logger.error(
+ "Unable to parse maven-metadata.xml from ${
+ mavenMetadata.url(
+ repositoryRootUrl
+ )
+ }",
+ it
+ )
+ }
+ }.getOrNull()
+ }
+ }
+
+ suspend fun getGradleModule(artifact: A): FileData? =
+ getGradleModule(artifact.gradleMetadataFile())
+
+ suspend fun getGradleModule(file: RepoFile): FileData? = supervisorScope {
+ val url = file.url(repositoryRootUrl)
+ runCatchingIO {
+ logger.debug("Looking for gradle module in $url")
+ client.get(url).takeIf { it.status != HttpStatusCode.NotFound }?.bodyAsText()?.let { module ->
+ json.decodeFromString(module)
+ }?.let(file::data)
+ }.onFailure {
+ logger.error("Unable to extract Gradle metadata file from $url", it)
+ }.getOrNull()
+ }
+
+ suspend fun getMavenPom(artifact: A): FileData? = getMavenPom(artifact.mavenPomFile())
+
+ suspend fun getMavenPom(file: RepoFile): FileData? = supervisorScope {
+ val url = file.url(repositoryRootUrl)
+ runCatchingIO {
+ client.get(url).takeIf { it.status != HttpStatusCode.NotFound }?.bodyAsText()?.asDocument()
+ ?.let(file::data)
+ }.onFailure {
+ logger.error("Unable to extract Maven pom file from $url", it)
+ }.getOrNull()
+ }
+
+ suspend fun listRepositoryPath(dir: RepoDirectory): List? = supervisorScope {
+ val urls = dir.url(repositoryRootUrl).let { listOf(it + SEP, it) }
+ var result: List? = null
+ for (url in urls) {
+ result = runCatchingIO {
+ client.get(url)
+ .takeIf { it.status != HttpStatusCode.NotFound }
+ ?.bodyAsText()
+ ?.asDocument()
+ ?.let(::parsePage)
+ ?.map(dir::item)
+ }.onFailure {
+ logger.error("Failed to list repository path at $url", it)
+ }.getOrNull()
+ if (result != null) break
+ }
+ result
+ }
+
+ override fun close() {
+ client.close()
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/FileData.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/FileData.kt
new file mode 100644
index 0000000..7ae5a26
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/FileData.kt
@@ -0,0 +1,6 @@
+package dev.petuska.kodex.cli.cmd.scan.domain
+
+data class FileData(
+ val file: RepoFile,
+ val data: T,
+)
diff --git a/scanner/src/jvmMain/kotlin/scanner/domain/GradleModule.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/GradleModule.kt
similarity index 97%
rename from scanner/src/jvmMain/kotlin/scanner/domain/GradleModule.kt
rename to app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/GradleModule.kt
index 5577549..ca765e0 100644
--- a/scanner/src/jvmMain/kotlin/scanner/domain/GradleModule.kt
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/GradleModule.kt
@@ -1,4 +1,4 @@
-package scanner.domain
+package dev.petuska.kodex.cli.cmd.scan.domain
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/RepoItem.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/RepoItem.kt
new file mode 100644
index 0000000..32d33e3
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/RepoItem.kt
@@ -0,0 +1,91 @@
+package dev.petuska.kodex.cli.cmd.scan.domain
+
+sealed class RepoItem(
+ name: String,
+) {
+ val name: String = name.removeSuffix(SEP).removePrefix(SEP)
+ abstract val directory: RepoDirectory
+
+ open val absolutePath: String by lazy {
+ "${directory.absolutePath.takeIf { it != SEP } ?: ""}$SEP${this.name}"
+ }
+
+ fun url(repositoryRootUrl: String) = "${repositoryRootUrl.removeSuffix(SEP)}$absolutePath"
+
+ override fun toString(): String = absolutePath
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is RepoItem) return false
+
+ if (absolutePath != other.absolutePath) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return absolutePath.hashCode()
+ }
+
+ companion object {
+ const val SEP = "/"
+ operator fun invoke(
+ name: String,
+ directory: RepoDirectory,
+ ): RepoItem = if (name.endsWith(SEP)) {
+ RepoDirectory(name, directory)
+ } else {
+ RepoFile(name, directory)
+ }
+ }
+}
+
+class RepoFile(
+ name: String,
+ override val directory: RepoDirectory,
+) : RepoItem(name) {
+ fun data(data: T) = FileData(this, data)
+}
+
+abstract class RepoDirectory private constructor(
+ name: String,
+) : RepoItem(name) {
+ companion object {
+ operator fun invoke(
+ name: String,
+ directory: RepoDirectory,
+ ): RepoDirectory = object : RepoDirectory(name) {
+ override val directory: RepoDirectory = directory
+ }
+
+ fun fromPath(path: String): RepoDirectory {
+ val nPath = path.removePrefix(SEP + SEP).removePrefix(SEP).removeSuffix(SEP)
+ return if (nPath == SEP || nPath.isBlank()) {
+ Root
+ } else if (!nPath.contains(SEP)) {
+ RepoDirectory(nPath, Root)
+ } else {
+ val chunks = nPath.split(SEP)
+ val name = chunks.last()
+ val parentPath = chunks.dropLast(1).joinToString(SEP)
+ RepoDirectory(name, fromPath(parentPath))
+ }
+ }
+ }
+
+ fun list(items: Collection): Listed = Listed(name, this, items)
+
+ fun dir(name: String) = RepoDirectory(name, this)
+ fun file(name: String) = RepoFile(name, this)
+ fun item(name: String) = RepoItem.invoke(name, this)
+
+ object Root : RepoDirectory("") {
+ override val directory: RepoDirectory get() = this
+ override val absolutePath: String = SEP
+ }
+
+ class Listed(
+ name: String,
+ override val directory: RepoDirectory,
+ val items: Collection
+ ) : RepoDirectory(name)
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/Repository.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/Repository.kt
new file mode 100644
index 0000000..713a3d6
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/Repository.kt
@@ -0,0 +1,48 @@
+package dev.petuska.kodex.cli.cmd.scan.domain
+
+import dev.petuska.kodex.cli.cmd.scan.client.ArtifactoryClient
+import dev.petuska.kodex.cli.cmd.scan.client.JBossClient
+import dev.petuska.kodex.cli.cmd.scan.client.MavenRepositoryClient
+import io.ktor.client.HttpClient
+import kotlinx.serialization.json.Json
+
+enum class Repository(
+ val alias: String,
+ val url: String,
+ val client: (
+ url: String,
+ client: HttpClient,
+ json: Json,
+ ) -> MavenRepositoryClient,
+) {
+ MAVEN_CENTRAL(
+ "mavenCentral",
+ "https://repo1.maven.org/maven2",
+ ::ArtifactoryClient
+ ),
+ GRADLE_PLUGIN_PORTAL(
+ "gradlePluginPortal",
+ "https://plugins.gradle.org/m2",
+ ::ArtifactoryClient
+ ),
+ SPRING(
+ "spring",
+ "https://repo.spring.io/release",
+ ::ArtifactoryClient
+ ),
+ ATLASSIAN(
+ "atlassian",
+ "https://packages.atlassian.com/content/repositories/atlassian-public",
+ ::ArtifactoryClient
+ ),
+ J_BOSS(
+ "jBoss",
+ "https://repository.jboss.org/nexus/content/repositories/releases",
+ ::JBossClient
+ ),
+ HORTON_WORKS(
+ "hortonWorks",
+ "https://repo.hortonworks.com/content/repositories/releases",
+ ::JBossClient
+ ),
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/SimpleMavenArtefact.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/SimpleMavenArtefact.kt
new file mode 100644
index 0000000..14bd20b
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/domain/SimpleMavenArtefact.kt
@@ -0,0 +1,14 @@
+package dev.petuska.kodex.cli.cmd.scan.domain
+
+import dev.petuska.kodex.core.domain.MavenArtefact
+
+data class SimpleMavenArtefact(
+ override val group: String,
+ override val name: String,
+ override val latestVersion: String,
+ override val releaseVersion: String?,
+ override val versions: List?,
+ override val lastUpdated: Long?,
+) : MavenArtefact {
+ override fun toString(): String = "$group:$name"
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/processor/GradleModuleProcessor.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/processor/GradleModuleProcessor.kt
new file mode 100644
index 0000000..1bbe794
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/processor/GradleModuleProcessor.kt
@@ -0,0 +1,37 @@
+package dev.petuska.kodex.cli.cmd.scan.processor
+
+import dev.petuska.kodex.cli.cmd.scan.domain.GradleModule
+import dev.petuska.kodex.core.domain.KotlinTarget
+
+class GradleModuleProcessor {
+ val GradleModule.isRootModule
+ get() = component?.url == null
+
+ val GradleModule.supportedTargets
+ get(): Set? = variants?.mapNotNull { variant ->
+ variant.attributes?.let { attrs ->
+ when (attrs.orgJetbrainsKotlinPlatformType) {
+ "common" -> KotlinTarget.Common.let(::listOf)
+ "wasm" -> KotlinTarget.Wasm.let(::listOf)
+ "androidJvm" -> KotlinTarget.JVM.Android.let(::listOf)
+ "jvm" -> KotlinTarget.JVM.Java.let(::listOf)
+ "js" -> when (attrs.orgJetbrainsKotlinJsCompiler) {
+ "ir" -> KotlinTarget.JS.IR
+ "legacy" -> KotlinTarget.JS.Legacy
+ else -> KotlinTarget.JS.Legacy
+ }.let(::listOf)
+
+ "native" -> attrs.orgJetbrainsKotlinNativeTarget?.let { target ->
+ KotlinTarget.Native.values().find { target == it.id }?.let(::listOf)
+ } ?: run {
+ if (component?.module == "kotlin-test") KotlinTarget.Native.values() else null
+ }
+
+ else -> null
+ } ?: (attrs.orgJetbrainsKotlinNativeTarget ?: attrs.orgJetbrainsKotlinPlatformType)?.let(
+ KotlinTarget::Unknown
+ )
+ ?.let(::listOf)
+ }
+ }?.flatten()?.toSet()
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/processor/PomProcessor.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/processor/PomProcessor.kt
new file mode 100644
index 0000000..b3bd4bc
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/processor/PomProcessor.kt
@@ -0,0 +1,26 @@
+package dev.petuska.kodex.cli.cmd.scan.processor
+
+import org.jsoup.nodes.Document
+
+class PomProcessor {
+ val Document.description: String?
+ get() = selectFirst("project>description")?.text()
+
+ val Document.url: String?
+ get() = selectFirst("project>url")?.text()
+
+ val Document.scmUrl: String?
+ get() =
+ selectFirst("project>scm")?.let { scm ->
+ val url = run {
+ scm.selectFirst("url")?.text()
+ ?: scm.selectFirst("connection")?.text()
+ ?: scm.selectFirst("developerConnection")?.text()
+ }?.trim()
+
+ val path = url?.trim()?.split("://")?.getOrNull(1)
+ ?: url?.split("@")?.getOrNull(1)?.replaceFirst(":", "/")
+
+ path?.removeSuffix(".git")?.removeSuffix("/")?.let { u -> "https://$u.git" } ?: url
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/service/PageService.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/service/PageService.kt
new file mode 100644
index 0000000..f9a00a7
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/service/PageService.kt
@@ -0,0 +1,127 @@
+package dev.petuska.kodex.cli.cmd.scan.service
+
+import dev.petuska.kodex.cli.cmd.scan.client.MavenRepositoryClient
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoDirectory
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoItem
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoItem.Companion.SEP
+import dev.petuska.kodex.cli.util.LoggerDelegate
+import dev.petuska.kodex.cli.util.toHumanString
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.supervisorScope
+import kotlin.properties.Delegates
+import kotlin.time.Duration
+import kotlin.time.ExperimentalTime
+import kotlin.time.measureTime
+
+class PageService(
+ private val client: MavenRepositoryClient<*>,
+ private val delay: Duration,
+) {
+ private val logger by LoggerDelegate()
+ private val pathSeparator = Regex("[\\.$SEP\\\\]")
+
+ fun findPages(
+ path: String = "",
+ include: Collection = listOf(),
+ exclude: Collection = listOf(),
+ ): Flow = channelFlow {
+ scanPage(
+ RepoDirectory.fromPath(path),
+ include.map { it.removePrefix(SEP) },
+ exclude.map { it.removePrefix(SEP) },
+ )
+ }
+
+ private fun RepoItem.isIncluded(
+ include: List>,
+ exclude: List>,
+ ): Triple {
+ var explicit = false
+ var explicitChildren = false
+ val included = include.takeIf { it.isNotEmpty() }?.let { filter ->
+ filter.any { (match, next) ->
+ if (match.isNotBlank()) explicit = true
+ if (next == null) explicitChildren = true
+ if (next == null) {
+ absolutePath.equals(match, ignoreCase = true)
+ } else {
+ absolutePath.startsWith(match, ignoreCase = true)
+ }
+ }
+ } ?: true
+ val excluded = exclude.takeIf { it.isNotEmpty() }?.let { filter ->
+ filter.any { (match, next) ->
+ next.isNullOrBlank() && absolutePath.startsWith(match, ignoreCase = true)
+ }
+ } ?: false
+ return Triple((included && !excluded), explicit, explicitChildren)
+ }
+
+ private fun Collection.splitFirst(): List> = map {
+ val s = it.split(pathSeparator, limit = 2)
+ val next = s.getOrNull(1)
+ "$SEP${s[0]}" to if (next?.isBlank() == true) null else (next ?: "")
+ }
+
+ private suspend fun ProducerScope.scanPage(
+ page: RepoDirectory,
+ include: Collection,
+ exclude: Collection,
+ explicitChildren: Boolean = false,
+ ): Int {
+ return supervisorScope {
+ val cInclude = include.splitFirst()
+ val cExclude = exclude.splitFirst()
+ val includes = cInclude.map {
+ page.item(it.first).absolutePath to it.second
+ }
+ val excludes = cExclude.map {
+ page.item(it.first).absolutePath to it.second
+ }
+
+ val items = client.listRepositoryPath(page) ?: listOf()
+ send(page.list(items))
+ // Try to avoid at least some subpages for versions
+ val modulePage = items.any { it.name == "maven-metadata.xml" }
+ val directories = items.filterIsInstance().mapNotNull { item ->
+ val (included, explicit, explicitC) = item.isIncluded(includes, excludes)
+ item.takeIf { included && (!modulePage || !item.name.getOrElse(0) { 'a' }.isDigit()) }
+ ?.let { Triple(it, explicit, explicitC) }
+ }
+
+ directories.map { (item, explicit, explicitC) ->
+ logger.debug("Found page [${item.name}] in $page")
+ async {
+ if (explicit || explicitChildren) {
+ logger.info("Scanning included page tree in $item")
+ } else {
+ logger.debug("Looking for pages in $item")
+ }
+ var count by Delegates.notNull()
+
+ @OptIn(ExperimentalTime::class)
+ val duration = measureTime {
+ count = scanPage(
+ page = item,
+ include = cInclude.mapNotNull { it.second?.takeIf(String::isNotBlank) },
+ exclude = cExclude.mapNotNull { it.second?.takeIf(String::isNotBlank) },
+ explicitChildren = explicitC,
+ )
+ }
+ if (explicit || explicitChildren) {
+ logger.info(
+ "Finished scanning included page tree at $item in " +
+ "${duration.toHumanString()} and found $count subpages"
+ )
+ }
+ count
+ }.also { delay(delay) }
+ }.awaitAll().sum() + directories.size
+ }
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/service/SimpleMavenArtefactService.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/service/SimpleMavenArtefactService.kt
new file mode 100644
index 0000000..29c6728
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/service/SimpleMavenArtefactService.kt
@@ -0,0 +1,53 @@
+package dev.petuska.kodex.cli.cmd.scan.service
+
+import dev.petuska.kodex.cli.cmd.scan.client.MavenRepositoryClient
+import dev.petuska.kodex.cli.cmd.scan.domain.FileData
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoDirectory
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoFile
+import dev.petuska.kodex.cli.cmd.scan.domain.SimpleMavenArtefact
+import dev.petuska.kodex.cli.cmd.scan.processor.GradleModuleProcessor
+import dev.petuska.kodex.cli.cmd.scan.processor.PomProcessor
+import dev.petuska.kodex.cli.util.LoggerDelegate
+import dev.petuska.kodex.core.domain.KotlinLibrary
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+
+class SimpleMavenArtefactService(
+ private val client: MavenRepositoryClient,
+) {
+ private val logger by LoggerDelegate()
+
+ fun findMavenArtefacts(pages: Flow): Flow> =
+ pages.mapNotNull { page ->
+ logger.debug("Looking for maven-metadata.xml in $page")
+ val files = page.items.filterIsInstance()
+ val artefact = files.find { it.name == "maven-metadata.xml" }?.let {
+ client.getMavenArtefact(it)
+ }
+ artefact?.also {
+ logger.debug("Found maven artefact [$it] in $page")
+ }
+ }
+
+ fun findKotlinLibraries(
+ artefacts: Flow>
+ ): Flow> = artefacts.mapNotNull { (_, artefact) ->
+ with(GradleModuleProcessor()) {
+ val module = client.getGradleModule(artefact)?.takeIf { it.data.isRootModule }
+ val supportedTargets = module?.data?.supportedTargets?.takeIf { it.isNotEmpty() }
+ supportedTargets?.let {
+ client.getMavenPom(artefact)?.data?.let { pom ->
+ with(PomProcessor()) {
+ KotlinLibrary(
+ targets = it,
+ artefact = artefact,
+ description = pom.description,
+ website = pom.url,
+ scm = pom.scmUrl,
+ ).let(module.file::data)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/cmd/scan/util/mavenArtefact.kt b/app/app-cli/src/jvmMain/kotlin/cmd/scan/util/mavenArtefact.kt
new file mode 100644
index 0000000..14a8730
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/cmd/scan/util/mavenArtefact.kt
@@ -0,0 +1,23 @@
+package dev.petuska.kodex.cli.cmd.scan.util
+
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoDirectory
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoItem.Companion.SEP
+import dev.petuska.kodex.core.domain.MavenArtefact
+
+fun MavenArtefact.gradleMetadataFileName(version: String = this.version): String =
+ "$name-$version.module"
+
+fun MavenArtefact.mavenPomFileName(version: String = this.version): String = "$name-$version.pom"
+
+fun MavenArtefact.moduleRootDirectory() = RepoDirectory.fromPath(
+ "${group.replace(".", SEP)}$SEP$name",
+)
+
+fun MavenArtefact.moduleVersionDirectory(version: String = this.version) =
+ moduleRootDirectory().dir(version)
+
+fun MavenArtefact.gradleMetadataFile(version: String = this.version) =
+ moduleVersionDirectory(version).file(gradleMetadataFileName(version))
+
+fun MavenArtefact.mavenPomFile(version: String = latestVersion) =
+ moduleVersionDirectory(version).file(mavenPomFileName(version))
diff --git a/app/app-cli/src/jvmMain/kotlin/config/di.kt b/app/app-cli/src/jvmMain/kotlin/config/di.kt
new file mode 100644
index 0000000..ad35734
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/config/di.kt
@@ -0,0 +1,84 @@
+package dev.petuska.kodex.cli.config
+
+import dev.petuska.kodex.cli.cmd.KodexCmd
+import dev.petuska.kodex.cli.cmd.capture.CaptureCmd
+import dev.petuska.kodex.cli.cmd.scan.ScanCmd
+import dev.petuska.kodex.cli.util.PrivateEnv
+import io.ktor.client.*
+import io.ktor.client.engine.cio.*
+import io.ktor.client.plugins.*
+import io.ktor.client.plugins.auth.*
+import io.ktor.client.plugins.auth.providers.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.client.request.*
+import io.ktor.http.*
+import io.ktor.serialization.kotlinx.cbor.*
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.serialization.ExperimentalSerializationApi
+import org.koin.core.module.dsl.onClose
+import org.koin.core.module.dsl.singleOf
+import org.koin.core.module.dsl.withOptions
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.DurationUnit
+
+val cmdModule = module {
+ singleOf(::KodexCmd)
+ singleOf(::ScanCmd)
+ singleOf(::CaptureCmd)
+}
+
+val clientsModule = module {
+ factory {
+ HttpClient(CIO) {
+ val timeout = 2.5.minutes.toLong(DurationUnit.MILLISECONDS)
+ engine {
+ requestTimeout = timeout
+ maxConnectionsCount = 64
+ }
+ defaultRequest {
+ contentType(ContentType.Application.Json)
+ accept(ContentType.Application.Json)
+ accept(ContentType.Application.Cbor)
+ accept(ContentType.Text.Html)
+ }
+ install(HttpTimeout) {
+ requestTimeoutMillis = timeout
+ connectTimeoutMillis = timeout
+ socketTimeoutMillis = timeout
+ }
+ install(HttpRequestRetry) {
+ retryOnServerErrors(maxRetries = 3)
+ retryOnException(maxRetries = 3)
+ exponentialDelay()
+ }
+ install(ContentNegotiation) {
+ json(get(named("pretty")))
+ @OptIn(ExperimentalSerializationApi::class)
+ cbor(get())
+ }
+ }
+ } withOptions {
+ onClose { it?.close() }
+ }
+ factory(named("kodex")) {
+ get().config {
+ defaultRequest {
+ url(PrivateEnv.API_URL)
+ }
+ install(Auth) {
+ basic {
+ credentials {
+ BasicAuthCredentials(
+ username = PrivateEnv.ADMIN_USER,
+ password = PrivateEnv.ADMIN_PASSWORD
+ )
+ }
+ }
+ }
+ }
+ } withOptions {
+ onClose { it?.close() }
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/main.kt b/app/app-cli/src/jvmMain/kotlin/main.kt
new file mode 100644
index 0000000..24e2900
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/main.kt
@@ -0,0 +1,25 @@
+package dev.petuska.kodex.cli
+
+import dev.petuska.kodex.cli.cmd.KodexCmd
+import dev.petuska.kodex.cli.config.clientsModule
+import dev.petuska.kodex.cli.config.cmdModule
+import dev.petuska.kodex.cli.util.PrivateEnv
+import dev.petuska.kodex.core.config.serialisationModule
+import dev.petuska.kodex.repository.config.repositoryModule
+import org.koin.core.context.startKoin
+import org.koin.core.context.stopKoin
+import org.koin.logger.slf4jLogger
+
+fun main(args: Array) {
+ val koin = startKoin {
+ slf4jLogger()
+ modules(
+ serialisationModule,
+ repositoryModule(PrivateEnv.MONGO_STRING, PrivateEnv.MONGO_DATABASE),
+ clientsModule,
+ cmdModule,
+ )
+ }.koin
+ koin.get().main(args)
+ stopKoin()
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/util/LoggerDelegate.kt b/app/app-cli/src/jvmMain/kotlin/util/LoggerDelegate.kt
new file mode 100644
index 0000000..08c53c4
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/util/LoggerDelegate.kt
@@ -0,0 +1,15 @@
+package dev.petuska.kodex.cli.util
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+class LoggerDelegate : ReadOnlyProperty {
+ override fun getValue(thisRef: R, property: KProperty<*>): Logger {
+ val javaClass = thisRef.javaClass.let { java ->
+ java.enclosingClass ?: java
+ }
+ return LoggerFactory.getLogger(javaClass)
+ }
+}
diff --git a/app/app-cli/src/jvmMain/kotlin/util/PrivateEnv.kt b/app/app-cli/src/jvmMain/kotlin/util/PrivateEnv.kt
new file mode 100644
index 0000000..ff087a2
--- /dev/null
+++ b/app/app-cli/src/jvmMain/kotlin/util/PrivateEnv.kt
@@ -0,0 +1,11 @@
+package dev.petuska.kodex.cli.util
+
+import dev.petuska.kodex.core.util.Env
+
+object PrivateEnv : Env() {
+ val MONGO_STRING by EnvDelegate { it ?: "mongodb://localhost:27017" }
+ val MONGO_DATABASE by EnvDelegate { it ?: "kodex" }
+ val API_URL by EnvDelegate { it ?: "http://localhost:8080" }
+ val ADMIN_USER by EnvDelegate { it ?: "admin" }
+ val ADMIN_PASSWORD by EnvDelegate { it ?: "admin" }
+}
diff --git a/scanner/src/jvmMain/kotlin/scanner/util/general.kt b/app/app-cli/src/jvmMain/kotlin/util/general.kt
similarity index 54%
rename from scanner/src/jvmMain/kotlin/scanner/util/general.kt
rename to app/app-cli/src/jvmMain/kotlin/util/general.kt
index 219485e..6def074 100644
--- a/scanner/src/jvmMain/kotlin/scanner/util/general.kt
+++ b/app/app-cli/src/jvmMain/kotlin/util/general.kt
@@ -1,16 +1,14 @@
-package scanner.util
+package dev.petuska.kodex.cli.util
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import kotlinx.serialization.json.Json
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
-
-val rawJson = Json { ignoreUnknownKeys = true }
-val prettyJson = Json {
- prettyPrint = true
- ignoreUnknownKeys = true
-}
+import kotlin.time.Duration
suspend fun String.asDocument(): Document =
withContext(Dispatchers.IO) { Jsoup.parse(this@asDocument) }
+
+fun Duration.toHumanString() = toComponents { hours, minutes, seconds, nanoseconds ->
+ "${hours}h ${minutes}m $seconds.${nanoseconds}s"
+}
diff --git a/app/app-cli/src/jvmTest/kotlin/Sandbox.kt b/app/app-cli/src/jvmTest/kotlin/Sandbox.kt
new file mode 100644
index 0000000..19a256c
--- /dev/null
+++ b/app/app-cli/src/jvmTest/kotlin/Sandbox.kt
@@ -0,0 +1,10 @@
+package dev.petuska.kodex.cli
+
+import kotlin.test.Test
+
+class Sandbox {
+ @Test
+ fun sandboxTest() {
+ println("[TEST] Sandbox")
+ }
+}
diff --git a/app/app-cli/src/jvmTest/kotlin/cmd/scan/domain/RepoItemTest.kt b/app/app-cli/src/jvmTest/kotlin/cmd/scan/domain/RepoItemTest.kt
new file mode 100644
index 0000000..f62a863
--- /dev/null
+++ b/app/app-cli/src/jvmTest/kotlin/cmd/scan/domain/RepoItemTest.kt
@@ -0,0 +1,52 @@
+package dev.petuska.kodex.cli.cmd.scan.domain
+
+import dev.petuska.kodex.cli.cmd.scan.domain.RepoItem.Companion.SEP
+import dev.petuska.kodex.test.dynamicTests
+import io.kotest.matchers.shouldBe
+import kotlin.test.Test
+
+class RepoItemTest {
+ @Test
+ fun directory() = dynamicTests {
+ listOf(
+ "/",
+ "/dir",
+ "/dir/sub/"
+ ).forEach { path ->
+ "[$path] should be parsed into RepoDirectory" {
+ val dir = RepoDirectory.fromPath(path)
+ dir.absolutePath shouldBe if (path.length <= 1) SEP else path.removeSuffix(SEP)
+ }
+ }
+ }
+
+ @Test
+ fun directoryChildren() = dynamicTests {
+ val parents = listOf("/", "/dir", "/dir/sub/").map(RepoDirectory::fromPath)
+ val children = listOf(
+ "",
+ "/",
+ "child",
+ "/child",
+ "/child/",
+ )
+ parents.forEach { parent ->
+ children.forEach { child ->
+ "Parent [$parent] should be able to nest child [$child]" {
+ val next = parent.item(child)
+ val cName = child.removePrefix(SEP).removeSuffix(SEP)
+ next.absolutePath shouldBe "${parent.absolutePath.takeIf { it != SEP } ?: ""}$SEP$cName"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun item() = dynamicTests {
+ val parent = RepoDirectory.Root
+ "/dev/petuska/gradle-kotlin-delegates/maven-metadata.xml" {
+ val next = parent.item("/dev/petuska/gradle-kotlin-delegates/maven-metadata.xml")
+ (next is RepoFile) shouldBe true
+ }
+ }
+}
diff --git a/app/app-cli/src/jvmTest/kotlin/cmd/scan/processor/GradleModuleProcessorTest.kt b/app/app-cli/src/jvmTest/kotlin/cmd/scan/processor/GradleModuleProcessorTest.kt
new file mode 100644
index 0000000..2838cf4
--- /dev/null
+++ b/app/app-cli/src/jvmTest/kotlin/cmd/scan/processor/GradleModuleProcessorTest.kt
@@ -0,0 +1,64 @@
+package dev.petuska.kodex.cli.cmd.scan.processor
+
+import dev.petuska.kodex.cli.cmd.scan.domain.GradleModule
+import dev.petuska.kodex.cli.testutil.parseJsonFile
+import dev.petuska.kodex.core.domain.KotlinTarget
+import dev.petuska.kodex.test.dynamicTests
+import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
+import io.kotest.matchers.shouldBe
+import kotlin.test.Test
+
+class GradleModuleProcessorTest {
+ @Test
+ fun tests() = dynamicTests {
+ val module = parseJsonFile("presenter-middleware-0.2.10.module")
+
+ dynamicTest("isRootModule") {
+ with(GradleModuleProcessor()) {
+ module.isRootModule shouldBe true
+ module.copy(component = module.component?.copy(url = "http")).isRootModule shouldBe false
+ }
+ }
+ dynamicTest("listSupportedTargets") {
+ with(GradleModuleProcessor()) {
+ val targets = module.supportedTargets
+
+ targets shouldContainExactlyInAnyOrder setOf(
+ KotlinTarget.JVM.Android,
+ KotlinTarget.JVM.Java,
+ KotlinTarget.JVM.Java,
+ KotlinTarget.Native.IOS.IOSArm64,
+ KotlinTarget.Native.IOS.IOSX64,
+ KotlinTarget.Common,
+ )
+
+ val targets1 = parseJsonFile("redux-kotlin-0.5.5.module").supportedTargets
+ targets1 shouldContainExactlyInAnyOrder setOf(
+ KotlinTarget.Native.AndroidNative.AndroidNativeArm32,
+ KotlinTarget.Native.AndroidNative.AndroidNativeArm64,
+ KotlinTarget.Native.IOS.IOSArm32,
+ KotlinTarget.Native.IOS.IOSArm64,
+ KotlinTarget.Native.IOS.IOSX64,
+ KotlinTarget.Native.WatchOS.WatchOSArm32,
+ KotlinTarget.Native.WatchOS.WatchOSArm64,
+ KotlinTarget.Native.WatchOS.WatchOSX86,
+ KotlinTarget.Native.Wasm32,
+ KotlinTarget.Native.TvOS.TvOSArm64,
+ KotlinTarget.Native.TvOS.TvOSX64,
+ KotlinTarget.Native.Mingw.MingwX64,
+ KotlinTarget.Native.Mingw.MingwX86,
+ KotlinTarget.Native.MacOS.MacOSX64,
+ KotlinTarget.Native.Linux.LinuxX64,
+ KotlinTarget.Native.Linux.LinuxMipsel32,
+ KotlinTarget.Native.Linux.LinuxMips32,
+ KotlinTarget.Native.Linux.LinuxArm64,
+ KotlinTarget.Native.Linux.LinuxArm32Hfp,
+ KotlinTarget.Common,
+ KotlinTarget.JVM.Java,
+ KotlinTarget.JS.IR,
+ KotlinTarget.JS.Legacy,
+ )
+ }
+ }
+ }
+}
diff --git a/scanner/src/jvmTest/kotlin/scanner/processor/PomProcessorTest.kt b/app/app-cli/src/jvmTest/kotlin/cmd/scan/processor/PomProcessorTest.kt
similarity index 59%
rename from scanner/src/jvmTest/kotlin/scanner/processor/PomProcessorTest.kt
rename to app/app-cli/src/jvmTest/kotlin/cmd/scan/processor/PomProcessorTest.kt
index f3f1cca..d8a203b 100644
--- a/scanner/src/jvmTest/kotlin/scanner/processor/PomProcessorTest.kt
+++ b/app/app-cli/src/jvmTest/kotlin/cmd/scan/processor/PomProcessorTest.kt
@@ -1,32 +1,33 @@
-package scanner.processor
+package dev.petuska.kodex.cli.cmd.scan.processor
-import io.kotest.core.spec.style.FunSpec
+import dev.petuska.kodex.cli.testutil.parseXmlFile
+import dev.petuska.kodex.test.dynamicTests
import io.kotest.matchers.shouldBe
-import scanner.testutil.parseXmlFile
+import kotlin.test.Test
-class PomProcessorTest :
- FunSpec({
+class PomProcessorTest {
+ @Test
+ fun tests() = dynamicTests {
val pom = parseXmlFile("presenter-middleware-0.2.10.pom")
-
- test("getDescription") {
+ dynamicTest("getDescription") {
with(PomProcessor()) {
val description = pom.description
description shouldBe
- "Presenter middleware for updating views based on selectors & reselect for Redux-Kotlin. Mulitiplatform supported."
+ "Presenter middleware for updating views based on selectors & " +
+ "reselect for Redux-Kotlin. Mulitiplatform supported."
}
}
-
- test("getUrl") {
+ dynamicTest("getUrl") {
with(PomProcessor()) {
val description = pom.url
description shouldBe "https://github.com/reduxkotlin/presenter-middleware/"
}
}
-
- test("getScmUrl") {
+ dynamicTest("getScmUrl") {
with(PomProcessor()) {
val description = pom.scmUrl
description shouldBe "https://github.com/reduxkotlin/presenter-middleare.git"
}
}
- })
+ }
+}
diff --git a/app/app-cli/src/jvmTest/kotlin/testutil/resources.kt b/app/app-cli/src/jvmTest/kotlin/testutil/resources.kt
new file mode 100644
index 0000000..d91cafa
--- /dev/null
+++ b/app/app-cli/src/jvmTest/kotlin/testutil/resources.kt
@@ -0,0 +1,19 @@
+package dev.petuska.kodex.cli.testutil
+
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import org.jsoup.Jsoup
+
+val laxJson = Json { ignoreUnknownKeys = true }
+
+inline fun Any.parseJsonFile(path: String) =
+ this::class.java.classLoader.getResourceAsStream(path)?.let {
+ laxJson.decodeFromString(it.reader().use { r -> r.readText() })
+ } ?: error("Resource $path not found")
+
+fun Any.parseJsonFile(path: String) = parseJsonFile(path)
+
+fun Any.parseXmlFile(path: String) =
+ this::class.java.classLoader.getResourceAsStream(path)?.let {
+ Jsoup.parse(it.reader().use { r -> r.readText() })
+ } ?: error("Resource $path not found")
diff --git a/app/app-cli/src/jvmTest/resources/klip-core-0.3.0.module b/app/app-cli/src/jvmTest/resources/klip-core-0.3.0.module
new file mode 100644
index 0000000..8d5d6b1
--- /dev/null
+++ b/app/app-cli/src/jvmTest/resources/klip-core-0.3.0.module
@@ -0,0 +1,808 @@
+{
+ "formatVersion": "1.1",
+ "component": {
+ "group": "dev.petuska",
+ "module": "klip-core",
+ "version": "0.3.0",
+ "attributes": {
+ "org.gradle.status": "release"
+ }
+ },
+ "createdBy": {
+ "gradle": {
+ "version": "7.3"
+ }
+ },
+ "variants": [
+ {
+ "name": "metadataApiElements",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.platform.type": "common"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-test",
+ "version": {
+ "requires": "1.6.0"
+ }
+ },
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.0"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "klip-core-metadata-0.3.0-all.jar",
+ "url": "klip-core-0.3.0-all.jar",
+ "size": 21632,
+ "sha512": "933c85b488a48120cf9f22c56795c344d468a424c87343dbc150d03c10358bfe0994f27aa64e3b7992c5c7427c9076e6b61eec84300014e4ad4428745b477aa6",
+ "sha256": "16755204b444a0d48b5276e1d2f4e63cc58a83a4590578ad3a875868da6c047f",
+ "sha1": "31eb30dc0c46a35b1cb91cb97e25ee8e7cb45385",
+ "md5": "3b1b0db3d719d652cfb61f589c58e021"
+ }
+ ]
+ },
+ {
+ "name": "commonMainMetadataElements",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.platform.type": "common"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-test",
+ "version": {
+ "requires": "1.6.0"
+ }
+ },
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.0"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "klip-core-0.3.0.jar",
+ "url": "klip-core-0.3.0.jar",
+ "size": 5526,
+ "sha512": "ff70037ceb3e32b1e42de0b97795454aec8d1bde4c583a38dcdb3324e27dbc45ebc4e4ca8dbb38f3d39dbff3c7717406cccbdb07cb7068856aa42f42e7a8a053",
+ "sha256": "d992515b3fec9777fb36944ed51b19cb277cd7a0e5dd575beedbffc8f64c72e2",
+ "sha1": "02d9f55ff886404ee74fa9368ab311055f32c0c5",
+ "md5": "ccd6919f2205c06c1d6e597562dff334"
+ }
+ ]
+ },
+ {
+ "name": "debugApiElements-published",
+ "attributes": {
+ "com.android.build.api.attributes.BuildTypeAttr": "debug",
+ "org.gradle.category": "library",
+ "org.gradle.jvm.environment": "android",
+ "org.gradle.usage": "java-api",
+ "org.jetbrains.kotlin.platform.type": "androidJvm"
+ },
+ "available-at": {
+ "url": "../../klip-core-android-debug/0.3.0/klip-core-android-debug-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-android-debug",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "debugRuntimeElements-published",
+ "attributes": {
+ "com.android.build.api.attributes.BuildTypeAttr": "debug",
+ "org.gradle.category": "library",
+ "org.gradle.jvm.environment": "android",
+ "org.gradle.usage": "java-runtime",
+ "org.jetbrains.kotlin.platform.type": "androidJvm"
+ },
+ "available-at": {
+ "url": "../../klip-core-android-debug/0.3.0/klip-core-android-debug-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-android-debug",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "releaseApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.jvm.environment": "android",
+ "org.gradle.usage": "java-api",
+ "org.jetbrains.kotlin.platform.type": "androidJvm"
+ },
+ "available-at": {
+ "url": "../../klip-core-android/0.3.0/klip-core-android-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-android",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "releaseRuntimeElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.jvm.environment": "android",
+ "org.gradle.usage": "java-runtime",
+ "org.jetbrains.kotlin.platform.type": "androidJvm"
+ },
+ "available-at": {
+ "url": "../../klip-core-android/0.3.0/klip-core-android-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-android",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "androidNativeArm32ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "android_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-androidnativearm32/0.3.0/klip-core-androidnativearm32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-androidnativearm32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "androidNativeArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "android_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-androidnativearm64/0.3.0/klip-core-androidnativearm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-androidnativearm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosArm32ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iosarm32/0.3.0/klip-core-iosarm32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iosarm32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosArm32MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iosarm32/0.3.0/klip-core-iosarm32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iosarm32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosArm64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iosarm64/0.3.0/klip-core-iosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosArm64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iosarm64/0.3.0/klip-core-iosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosSimulatorArm64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iossimulatorarm64/0.3.0/klip-core-iossimulatorarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iossimulatorarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosSimulatorArm64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iossimulatorarm64/0.3.0/klip-core-iossimulatorarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iossimulatorarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosX64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iosx64/0.3.0/klip-core-iosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "iosX64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-iosx64/0.3.0/klip-core-iosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-iosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "jsApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.js.compiler": "ir",
+ "org.jetbrains.kotlin.platform.type": "js"
+ },
+ "available-at": {
+ "url": "../../klip-core-js/0.3.0/klip-core-js-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-js",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "jsRuntimeElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-runtime",
+ "org.jetbrains.kotlin.js.compiler": "ir",
+ "org.jetbrains.kotlin.platform.type": "js"
+ },
+ "available-at": {
+ "url": "../../klip-core-js/0.3.0/klip-core-js-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-js",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "jvmApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.jvm.environment": "standard-jvm",
+ "org.gradle.libraryelements": "jar",
+ "org.gradle.usage": "java-api",
+ "org.jetbrains.kotlin.platform.type": "jvm"
+ },
+ "available-at": {
+ "url": "../../klip-core-jvm/0.3.0/klip-core-jvm-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-jvm",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "jvmRuntimeElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.jvm.environment": "standard-jvm",
+ "org.gradle.libraryelements": "jar",
+ "org.gradle.usage": "java-runtime",
+ "org.jetbrains.kotlin.platform.type": "jvm"
+ },
+ "available-at": {
+ "url": "../../klip-core-jvm/0.3.0/klip-core-jvm-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-jvm",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "linuxArm32HfpApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_arm32_hfp",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-linuxarm32hfp/0.3.0/klip-core-linuxarm32hfp-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-linuxarm32hfp",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "linuxArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-linuxarm64/0.3.0/klip-core-linuxarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-linuxarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "linuxMips32ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_mips32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-linuxmips32/0.3.0/klip-core-linuxmips32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-linuxmips32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "linuxMips32MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "linux_mips32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-linuxmips32/0.3.0/klip-core-linuxmips32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-linuxmips32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "linuxMipsel32ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_mipsel32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-linuxmipsel32/0.3.0/klip-core-linuxmipsel32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-linuxmipsel32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "linuxMipsel32MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "linux_mipsel32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-linuxmipsel32/0.3.0/klip-core-linuxmipsel32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-linuxmipsel32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "linuxX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-linuxx64/0.3.0/klip-core-linuxx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-linuxx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "macosArm64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "macos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-macosarm64/0.3.0/klip-core-macosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-macosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "macosArm64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "macos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-macosarm64/0.3.0/klip-core-macosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-macosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "macosX64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "macos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-macosx64/0.3.0/klip-core-macosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-macosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "macosX64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "macos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-macosx64/0.3.0/klip-core-macosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-macosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "mingwX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "mingw_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-mingwx64/0.3.0/klip-core-mingwx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-mingwx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "mingwX86ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "mingw_x86",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-mingwx86/0.3.0/klip-core-mingwx86-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-mingwx86",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "tvosArm64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "tvos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-tvosarm64/0.3.0/klip-core-tvosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-tvosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "tvosArm64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "tvos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-tvosarm64/0.3.0/klip-core-tvosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-tvosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "tvosSimulatorArm64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "tvos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-tvossimulatorarm64/0.3.0/klip-core-tvossimulatorarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-tvossimulatorarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "tvosSimulatorArm64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "tvos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-tvossimulatorarm64/0.3.0/klip-core-tvossimulatorarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-tvossimulatorarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "tvosX64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "tvos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-tvosx64/0.3.0/klip-core-tvosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-tvosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "tvosX64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "tvos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-tvosx64/0.3.0/klip-core-tvosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-tvosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosArm32ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosarm32/0.3.0/klip-core-watchosarm32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosarm32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosArm32MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosarm32/0.3.0/klip-core-watchosarm32-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosarm32",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosArm64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosarm64/0.3.0/klip-core-watchosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosArm64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosarm64/0.3.0/klip-core-watchosarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosSimulatorArm64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchossimulatorarm64/0.3.0/klip-core-watchossimulatorarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchossimulatorarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosSimulatorArm64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchossimulatorarm64/0.3.0/klip-core-watchossimulatorarm64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchossimulatorarm64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosX64ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosx64/0.3.0/klip-core-watchosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosX64MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosx64/0.3.0/klip-core-watchosx64-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosx64",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosX86ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_x86",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosx86/0.3.0/klip-core-watchosx86-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosx86",
+ "version": "0.3.0"
+ }
+ },
+ {
+ "name": "watchosX86MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_x86",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../klip-core-watchosx86/0.3.0/klip-core-watchosx86-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core-watchosx86",
+ "version": "0.3.0"
+ }
+ }
+ ]
+}
diff --git a/app/app-cli/src/jvmTest/resources/klip-core-linuxarm32hfp-0.3.0.module b/app/app-cli/src/jvmTest/resources/klip-core-linuxarm32hfp-0.3.0.module
new file mode 100644
index 0000000..babb6e3
--- /dev/null
+++ b/app/app-cli/src/jvmTest/resources/klip-core-linuxarm32hfp-0.3.0.module
@@ -0,0 +1,56 @@
+{
+ "formatVersion": "1.1",
+ "component": {
+ "url": "../../klip-core/0.3.0/klip-core-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core",
+ "version": "0.3.0",
+ "attributes": {
+ "org.gradle.status": "release"
+ }
+ },
+ "createdBy": {
+ "gradle": {
+ "version": "7.3"
+ }
+ },
+ "variants": [
+ {
+ "name": "linuxArm32HfpApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_arm32_hfp",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-test",
+ "version": {
+ "requires": "1.6.0"
+ }
+ },
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.0"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "klip-core.klib",
+ "url": "klip-core-linuxarm32hfp-0.3.0.klib",
+ "size": 25333,
+ "sha512": "031a86a46a87a616f649e57f26333f9ca755acb2fa64a2028df2fa41ccbc0a8664381c799d8e4bbbcdf06c63eb8afa0c7afd65bc1192bd5894c380241b174c6b",
+ "sha256": "d00508effee7fa4c2d6a12875b0c8e555d05cb9473930de24eac2486b1bac733",
+ "sha1": "b4aa7fa821299beebab6f84d4c15a20f05be5f9a",
+ "md5": "3d4317f3d8a38d9e2e2b5e77d184712b"
+ }
+ ]
+ }
+ ]
+}
diff --git a/app/app-cli/src/jvmTest/resources/klip-core-linuxmipsel32-0.3.0.module b/app/app-cli/src/jvmTest/resources/klip-core-linuxmipsel32-0.3.0.module
new file mode 100644
index 0000000..b9c4f74
--- /dev/null
+++ b/app/app-cli/src/jvmTest/resources/klip-core-linuxmipsel32-0.3.0.module
@@ -0,0 +1,93 @@
+{
+ "formatVersion": "1.1",
+ "component": {
+ "url": "../../klip-core/0.3.0/klip-core-0.3.0.module",
+ "group": "dev.petuska",
+ "module": "klip-core",
+ "version": "0.3.0",
+ "attributes": {
+ "org.gradle.status": "release"
+ }
+ },
+ "createdBy": {
+ "gradle": {
+ "version": "7.3"
+ }
+ },
+ "variants": [
+ {
+ "name": "linuxMipsel32ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_mipsel32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-test",
+ "version": {
+ "requires": "1.6.0"
+ }
+ },
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.0"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "klip-core.klib",
+ "url": "klip-core-linuxmipsel32-0.3.0.klib",
+ "size": 25324,
+ "sha512": "e4c138d6876d42c388dcc374345a203a2fa53fb8fb0bce900c69d8525f2388a9ffcafff6791db73596bef41de0a7b99c06238b3a4d11e67be13f9987c4e1e28e",
+ "sha256": "e0337c1cd6dc1b01123088c1a141a17672c6a277dcb4462d04713e5eae5c237e",
+ "sha1": "7340ea4fa9824abc672ef207d481cbcf5ad53249",
+ "md5": "8a5cebea9a48642a4f368c10514b2ae8"
+ }
+ ]
+ },
+ {
+ "name": "linuxMipsel32MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "linux_mipsel32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-test",
+ "version": {
+ "requires": "1.6.0"
+ }
+ },
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.0"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "klip-core-linuxmipsel32-0.3.0-metadata.jar",
+ "url": "klip-core-linuxmipsel32-0.3.0-metadata.jar",
+ "size": 261,
+ "sha512": "f949017f93182419b86f73020fe390f536b94cbc18eac508ae941b695731d5a3a7b826b75d67bcec2f98be57df61dd767e1e10e01c07bd510eb317ec7534816f",
+ "sha256": "dfe5d75ec8ce0ae22021a5afd1ce254e156f13c253a67fbdee574c98ee779b55",
+ "sha1": "c74d3422cbb68d31f6d35e8ece619e1711ca01ec",
+ "md5": "e41d1d34a325d35fd810a650dacacf9f"
+ }
+ ]
+ }
+ ]
+}
diff --git a/app/app-cli/src/jvmTest/resources/kodein-di-framework-ktor-server-jvm-7.12.0.module b/app/app-cli/src/jvmTest/resources/kodein-di-framework-ktor-server-jvm-7.12.0.module
new file mode 100644
index 0000000..ddd6eb5
--- /dev/null
+++ b/app/app-cli/src/jvmTest/resources/kodein-di-framework-ktor-server-jvm-7.12.0.module
@@ -0,0 +1,110 @@
+{
+ "formatVersion": "1.1",
+ "component": {
+ "group": "org.kodein.di",
+ "module": "kodein-di-framework-ktor-server-jvm",
+ "version": "7.12.0",
+ "attributes": {
+ "org.gradle.status": "release"
+ }
+ },
+ "createdBy": {
+ "gradle": {
+ "version": "7.4.2"
+ }
+ },
+ "variants": [
+ {
+ "name": "apiElements",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.dependency.bundling": "external",
+ "org.gradle.jvm.environment": "standard-jvm",
+ "org.gradle.jvm.version": 8,
+ "org.gradle.libraryelements": "jar",
+ "org.gradle.usage": "java-api",
+ "org.jetbrains.kotlin.platform.type": "jvm"
+ },
+ "dependencies": [
+ {
+ "group": "org.kodein.di",
+ "module": "kodein-di",
+ "version": {
+ "requires": "7.12.0"
+ }
+ },
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-jdk8",
+ "version": {
+ "requires": "1.6.20"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "kodein-di-framework-ktor-server-jvm-7.12.0.jar",
+ "url": "kodein-di-framework-ktor-server-jvm-7.12.0.jar",
+ "size": 22386,
+ "sha512": "608c0e77d6cedcc6f62125396df2eb24d8e4beec04444fbaad9d1237a3e5980b9957a11af60346287f4bb3175b4cb066b96c22f7aea165e620e14ec3b621af51",
+ "sha256": "60f03cf3d5b4fc9454bed48ece30d921921dc1a49084a56fb3f2e720188955d5",
+ "sha1": "c4947f85c8aa5e622d9aad49ef3ad1de27f40ff1",
+ "md5": "4ea7cce88b6ff38ff75e54c516b005a1"
+ }
+ ]
+ },
+ {
+ "name": "runtimeElements",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.dependency.bundling": "external",
+ "org.gradle.jvm.environment": "standard-jvm",
+ "org.gradle.jvm.version": 8,
+ "org.gradle.libraryelements": "jar",
+ "org.gradle.usage": "java-runtime",
+ "org.jetbrains.kotlin.platform.type": "jvm"
+ },
+ "dependencies": [
+ {
+ "group": "io.ktor",
+ "module": "ktor-server-core",
+ "version": {
+ "requires": "2.0.0"
+ }
+ },
+ {
+ "group": "io.ktor",
+ "module": "ktor-server-sessions",
+ "version": {
+ "requires": "2.0.0"
+ }
+ },
+ {
+ "group": "org.kodein.di",
+ "module": "kodein-di",
+ "version": {
+ "requires": "7.12.0"
+ }
+ },
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-jdk8",
+ "version": {
+ "requires": "1.6.20"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "kodein-di-framework-ktor-server-jvm-7.12.0.jar",
+ "url": "kodein-di-framework-ktor-server-jvm-7.12.0.jar",
+ "size": 22386,
+ "sha512": "608c0e77d6cedcc6f62125396df2eb24d8e4beec04444fbaad9d1237a3e5980b9957a11af60346287f4bb3175b4cb066b96c22f7aea165e620e14ec3b621af51",
+ "sha256": "60f03cf3d5b4fc9454bed48ece30d921921dc1a49084a56fb3f2e720188955d5",
+ "sha1": "c4947f85c8aa5e622d9aad49ef3ad1de27f40ff1",
+ "md5": "4ea7cce88b6ff38ff75e54c516b005a1"
+ }
+ ]
+ }
+ ]
+}
diff --git a/app/app-cli/src/jvmTest/resources/multiplatform-settings-iossimulatorarm64-1.0.0-alpha01.module b/app/app-cli/src/jvmTest/resources/multiplatform-settings-iossimulatorarm64-1.0.0-alpha01.module
new file mode 100644
index 0000000..c83a60b
--- /dev/null
+++ b/app/app-cli/src/jvmTest/resources/multiplatform-settings-iossimulatorarm64-1.0.0-alpha01.module
@@ -0,0 +1,79 @@
+{
+ "formatVersion": "1.1",
+ "component": {
+ "url": "../../multiplatform-settings/1.0.0-alpha01/multiplatform-settings-1.0.0-alpha01.module",
+ "group": "com.russhwolf",
+ "module": "multiplatform-settings",
+ "version": "1.0.0-alpha01",
+ "attributes": {
+ "org.gradle.status": "release"
+ }
+ },
+ "createdBy": {
+ "gradle": {
+ "version": "7.3.2"
+ }
+ },
+ "variants": [
+ {
+ "name": "iosSimulatorArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.21"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "multiplatform-settings.klib",
+ "url": "multiplatform-settings-iossimulatorarm64-1.0.0-alpha01.klib",
+ "size": 62962,
+ "sha512": "8cea45dbd31e219e04a98271201e2e5e3a9028ade49fd3995003acd9da5c36c582b23f17f46e77b33aeb659ff7bfbde3bc7ae4d187bb203866705789a41b8d52",
+ "sha256": "52599c89d644e7963fd2fc22451c4dd3c48310d8652acee5ce9d8011ab7fa29e",
+ "sha1": "75a954391be099bd30b28a346b5c157219515027",
+ "md5": "6cf255869d0aad0b1fdb0673cd4997f8"
+ }
+ ]
+ },
+ {
+ "name": "iosSimulatorArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.21"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "multiplatform-settings-iossimulatorarm64-1.0.0-alpha01-metadata.jar",
+ "url": "multiplatform-settings-iossimulatorarm64-1.0.0-alpha01-metadata.jar",
+ "size": 10929,
+ "sha512": "f786c64d4d23398be1466a68343f11120ee8f7b181bd2c67d0251b81348f021be50ccd5ce875185c81cf2c9981216d5afb9fa09989a02fd92789acd714460080",
+ "sha256": "580236f8e589da41551c869f96946299c25a8c31d9fb1553a2eba99292cd0f80",
+ "sha1": "1e3b59b9ffee6c7d1b0f23dcfc2d14d72069cccb",
+ "md5": "f37e6757e0c68113ee4405ed0316cbc2"
+ }
+ ]
+ }
+ ]
+}
diff --git a/scanner/src/jvmTest/resources/presenter-middleware-0.2.10.module b/app/app-cli/src/jvmTest/resources/presenter-middleware-0.2.10.module
similarity index 100%
rename from scanner/src/jvmTest/resources/presenter-middleware-0.2.10.module
rename to app/app-cli/src/jvmTest/resources/presenter-middleware-0.2.10.module
diff --git a/scanner/src/jvmTest/resources/presenter-middleware-0.2.10.pom b/app/app-cli/src/jvmTest/resources/presenter-middleware-0.2.10.pom
similarity index 100%
rename from scanner/src/jvmTest/resources/presenter-middleware-0.2.10.pom
rename to app/app-cli/src/jvmTest/resources/presenter-middleware-0.2.10.pom
diff --git a/scanner/src/jvmTest/resources/redux-kotlin-0.5.5.module b/app/app-cli/src/jvmTest/resources/redux-kotlin-0.5.5.module
similarity index 100%
rename from scanner/src/jvmTest/resources/redux-kotlin-0.5.5.module
rename to app/app-cli/src/jvmTest/resources/redux-kotlin-0.5.5.module
diff --git a/app/app-cli/src/jvmTest/resources/stately-concurrency-1.2.3.module b/app/app-cli/src/jvmTest/resources/stately-concurrency-1.2.3.module
new file mode 100644
index 0000000..9f7af23
--- /dev/null
+++ b/app/app-cli/src/jvmTest/resources/stately-concurrency-1.2.3.module
@@ -0,0 +1,768 @@
+{
+ "formatVersion": "1.1",
+ "component": {
+ "group": "co.touchlab",
+ "module": "stately-concurrency",
+ "version": "1.2.3",
+ "attributes": {
+ "org.gradle.status": "release"
+ }
+ },
+ "createdBy": {
+ "gradle": {
+ "version": "7.0.2"
+ }
+ },
+ "variants": [
+ {
+ "name": "metadataApiElements",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.platform.type": "common"
+ },
+ "dependencies": [
+ {
+ "group": "org.jetbrains.kotlin",
+ "module": "kotlin-stdlib-common",
+ "version": {
+ "requires": "1.6.20"
+ }
+ },
+ {
+ "group": "co.touchlab",
+ "module": "stately-common",
+ "version": {
+ "requires": "1.2.3"
+ }
+ }
+ ],
+ "files": [
+ {
+ "name": "stately-concurrency-metadata-1.2.3-all.jar",
+ "url": "stately-concurrency-1.2.3-all.jar",
+ "size": 20647,
+ "sha512": "c6482162bf54aeebca21d40740918964e2848b51e1af21d6a1d9d9d4d77669adf4b996e15c8f9dc8eb1426f1dce1f7aa35695b46bcdbb54a4be4b3b5476de9e2",
+ "sha256": "133ec0637e9741a5401e9e631f7671dbc94af6c246239f76b1a9f62cad457a06",
+ "sha1": "3a31e34659f92008aa4528fb053bfc3a71b7a6e3",
+ "md5": "0f60ad9056b9b0667bae0da62f6504d4"
+ }
+ ]
+ },
+ {
+ "name": "commonMainMetadataElements",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.platform.type": "common"
+ },
+ "files": [
+ {
+ "name": "stately-concurrency-1.2.3.jar",
+ "url": "stately-concurrency-1.2.3.jar",
+ "size": 5758,
+ "sha512": "a0ea576fc976c95e588dfccdfbe458558dac58fd8f8eec5e724192c3dd523b0daf73ba3d66c112f887bd75f15f9315fa285757e3eafc07f8e82b3c2cb9e9007d",
+ "sha256": "2731354f45bd0c8a8bd292d3f2e55dcc773dbec41f6a99392511de0a15bdccee",
+ "sha1": "d63390adea371fc70b2335e7e3d7c9d8c47b77c6",
+ "md5": "8bf20c3def272152835f04830b11dd2e"
+ }
+ ]
+ },
+ {
+ "name": "androidNativeArm32ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "android_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-androidnativearm32/1.2.3/stately-concurrency-androidnativearm32-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-androidnativearm32",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "androidNativeArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "android_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-androidnativearm64/1.2.3/stately-concurrency-androidnativearm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-androidnativearm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "androidNativeX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "android_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-androidnativex64/1.2.3/stately-concurrency-androidnativex64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-androidnativex64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "androidNativeX86ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "android_x86",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-androidnativex86/1.2.3/stately-concurrency-androidnativex86-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-androidnativex86",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosArm32ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iosarm32/1.2.3/stately-concurrency-iosarm32-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iosarm32",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosArm32MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iosarm32/1.2.3/stately-concurrency-iosarm32-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iosarm32",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iosarm64/1.2.3/stately-concurrency-iosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iosarm64/1.2.3/stately-concurrency-iosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosSimulatorArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iossimulatorarm64/1.2.3/stately-concurrency-iossimulatorarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iossimulatorarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosSimulatorArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iossimulatorarm64/1.2.3/stately-concurrency-iossimulatorarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iossimulatorarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "ios_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iosx64/1.2.3/stately-concurrency-iosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "iosX64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "ios_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-iosx64/1.2.3/stately-concurrency-iosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-iosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "jsLegacyApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.js.compiler": "legacy",
+ "org.jetbrains.kotlin.platform.type": "js"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-js/1.2.3/stately-concurrency-js-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-js",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "jsLegacyRuntimeElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-runtime",
+ "org.jetbrains.kotlin.js.compiler": "legacy",
+ "org.jetbrains.kotlin.platform.type": "js"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-js/1.2.3/stately-concurrency-js-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-js",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "jsIrApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.js.compiler": "ir",
+ "org.jetbrains.kotlin.platform.type": "js"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-js/1.2.3/stately-concurrency-js-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-js",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "jsIrRuntimeElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-runtime",
+ "org.jetbrains.kotlin.js.compiler": "ir",
+ "org.jetbrains.kotlin.platform.type": "js"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-js/1.2.3/stately-concurrency-js-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-js",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "jvmApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.libraryelements": "jar",
+ "org.gradle.usage": "java-api",
+ "org.jetbrains.kotlin.platform.type": "jvm"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-jvm/1.2.3/stately-concurrency-jvm-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-jvm",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "jvmRuntimeElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.libraryelements": "jar",
+ "org.gradle.usage": "java-runtime",
+ "org.jetbrains.kotlin.platform.type": "jvm"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-jvm/1.2.3/stately-concurrency-jvm-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-jvm",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "linuxArm32HfpApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_arm32_hfp",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-linuxarm32hfp/1.2.3/stately-concurrency-linuxarm32hfp-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-linuxarm32hfp",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "linuxMips32ApiElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_mips32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-linuxmips32/1.2.3/stately-concurrency-linuxmips32-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-linuxmips32",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "linuxMips32MetadataElements-published",
+ "attributes": {
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "linux_mips32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-linuxmips32/1.2.3/stately-concurrency-linuxmips32-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-linuxmips32",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "linuxX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "linux_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-linuxx64/1.2.3/stately-concurrency-linuxx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-linuxx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "macosArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "macos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-macosarm64/1.2.3/stately-concurrency-macosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-macosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "macosArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "macos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-macosarm64/1.2.3/stately-concurrency-macosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-macosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "macosX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "macos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-macosx64/1.2.3/stately-concurrency-macosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-macosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "macosX64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "macos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-macosx64/1.2.3/stately-concurrency-macosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-macosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "mingwX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "mingw_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-mingwx64/1.2.3/stately-concurrency-mingwx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-mingwx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "mingwX86ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "mingw_x86",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-mingwx86/1.2.3/stately-concurrency-mingwx86-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-mingwx86",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "tvosArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "tvos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-tvosarm64/1.2.3/stately-concurrency-tvosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-tvosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "tvosArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "tvos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-tvosarm64/1.2.3/stately-concurrency-tvosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-tvosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "tvosSimulatorArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "tvos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-tvossimulatorarm64/1.2.3/stately-concurrency-tvossimulatorarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-tvossimulatorarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "tvosSimulatorArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "tvos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-tvossimulatorarm64/1.2.3/stately-concurrency-tvossimulatorarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-tvossimulatorarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "tvosX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "tvos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-tvosx64/1.2.3/stately-concurrency-tvosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-tvosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "tvosX64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "tvos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-tvosx64/1.2.3/stately-concurrency-tvosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-tvosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosArm32ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosarm32/1.2.3/stately-concurrency-watchosarm32-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosarm32",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosArm32MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_arm32",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosarm32/1.2.3/stately-concurrency-watchosarm32-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosarm32",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosarm64/1.2.3/stately-concurrency-watchosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosarm64/1.2.3/stately-concurrency-watchosarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosSimulatorArm64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchossimulatorarm64/1.2.3/stately-concurrency-watchossimulatorarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchossimulatorarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosSimulatorArm64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_simulator_arm64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchossimulatorarm64/1.2.3/stately-concurrency-watchossimulatorarm64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchossimulatorarm64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosX64ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosx64/1.2.3/stately-concurrency-watchosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosX64MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_x64",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosx64/1.2.3/stately-concurrency-watchosx64-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosx64",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosX86ApiElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-api",
+ "org.jetbrains.kotlin.native.target": "watchos_x86",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosx86/1.2.3/stately-concurrency-watchosx86-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosx86",
+ "version": "1.2.3"
+ }
+ },
+ {
+ "name": "watchosX86MetadataElements-published",
+ "attributes": {
+ "artifactType": "org.jetbrains.kotlin.klib",
+ "org.gradle.category": "library",
+ "org.gradle.usage": "kotlin-metadata",
+ "org.jetbrains.kotlin.native.target": "watchos_x86",
+ "org.jetbrains.kotlin.platform.type": "native"
+ },
+ "available-at": {
+ "url": "../../stately-concurrency-watchosx86/1.2.3/stately-concurrency-watchosx86-1.2.3.module",
+ "group": "co.touchlab",
+ "module": "stately-concurrency-watchosx86",
+ "version": "1.2.3"
+ }
+ }
+ ]
+}
diff --git a/app/app-cli/src/jvmTest/resources/stately-concurrency_maven-metadata.xml b/app/app-cli/src/jvmTest/resources/stately-concurrency_maven-metadata.xml
new file mode 100644
index 0000000..9124981
--- /dev/null
+++ b/app/app-cli/src/jvmTest/resources/stately-concurrency_maven-metadata.xml
@@ -0,0 +1,35 @@
+
+
+ co.touchlab
+ stately-concurrency
+
+ 1.2.3
+ 1.2.3
+
+ 1.0.0-a2
+ 1.0.0-a3
+ 1.0.1-eap
+ 1.0.2
+ 1.0.3
+ 1.0.4-1.4-M2
+ 1.0.4-1.4-M3
+ 1.0.4-1.4.0-rc
+ 1.1.0
+ 1.1.1
+ 1.1.3
+ 1.1.4
+ 1.1.5
+ 1.1.6
+ 1.1.7
+ 1.1.9
+ 1.1.9-hmpp
+ 1.1.10
+ 1.2.0
+ 1.2.0-nmm
+ 1.2.1
+ 1.2.2
+ 1.2.3
+
+ 20220423135621
+
+
diff --git a/app/app-client/app-client-android/build.gradle.kts b/app/app-client/app-client-android/build.gradle.kts
new file mode 100644
index 0000000..afa6a62
--- /dev/null
+++ b/app/app-client/app-client-android/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ id("convention.app-android")
+ id("convention.compose")
+}
diff --git a/app/app-client/app-client-android/src/androidMain/AndroidManifest.xml b/app/app-client/app-client-android/src/androidMain/AndroidManifest.xml
new file mode 100644
index 0000000..2370809
--- /dev/null
+++ b/app/app-client/app-client-android/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/app-client/app-client-android/src/androidMain/kotlin/MainActivity.kt b/app/app-client/app-client-android/src/androidMain/kotlin/MainActivity.kt
new file mode 100644
index 0000000..450860e
--- /dev/null
+++ b/app/app-client/app-client-android/src/androidMain/kotlin/MainActivity.kt
@@ -0,0 +1,34 @@
+package dev.petuska.kodex.client
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import dev.petuska.kodex.client.config.loadEnv
+import dev.petuska.kodex.client.view.App
+import dev.petuska.kodex.client.view.AppTheme
+import dev.petuska.kodex.client.view.KodexApp
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+
+class MainActivity : AppCompatActivity() {
+ private val scope = MainScope()
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ scope.launch {
+ val env = loadEnv()
+ setContent {
+ KodexApp(env) {
+ AppTheme {
+ App()
+ }
+ }
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ scope.cancel()
+ }
+}
diff --git a/app/app-client/app-client-android/src/androidMain/kotlin/view/AppTheme.kt b/app/app-client/app-client-android/src/androidMain/kotlin/view/AppTheme.kt
new file mode 100644
index 0000000..e2238fc
--- /dev/null
+++ b/app/app-client/app-client-android/src/androidMain/kotlin/view/AppTheme.kt
@@ -0,0 +1,31 @@
+package dev.petuska.kodex.client.view
+
+import android.os.Build
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.platform.LocalContext
+import dev.petuska.kodex.client.util.select
+
+private val LightThemeColors = lightColorScheme()
+private val DarkThemeColors = darkColorScheme()
+
+@Composable
+fun AppTheme(content: @Composable () -> Unit) {
+ val darkTheme by select { darkTheme }
+ val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+ val colorScheme = when {
+ dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
+ dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
+ darkTheme -> DarkThemeColors
+ else -> LightThemeColors
+ }
+ MaterialTheme(
+ colorScheme = colorScheme,
+ content = content
+ )
+}
diff --git a/app/app-client/app-client-android/src/androidMain/res/mipmap-mdpi/kamp.png b/app/app-client/app-client-android/src/androidMain/res/mipmap-mdpi/kamp.png
new file mode 100644
index 0000000..006fea2
Binary files /dev/null and b/app/app-client/app-client-android/src/androidMain/res/mipmap-mdpi/kamp.png differ
diff --git a/app/app-client/app-client-android/src/androidMain/res/values/strings.xml b/app/app-client/app-client-android/src/androidMain/res/values/strings.xml
new file mode 100644
index 0000000..7fbd153
--- /dev/null
+++ b/app/app-client/app-client-android/src/androidMain/res/values/strings.xml
@@ -0,0 +1,14 @@
+
+ Kodex
+ Loading images...
+ Repository is empty.
+ No internet access.
+ List of images in current repository is invalid or empty.
+ Cannot refresh images.
+ Cannot load full size image.
+ This is last image.
+ This is first image.
+ Picture:
+ Size:
+ pixels.
+
\ No newline at end of file
diff --git a/app/app-client/app-client-desktop/build.gradle.kts b/app/app-client/app-client-desktop/build.gradle.kts
new file mode 100644
index 0000000..149d66d
--- /dev/null
+++ b/app/app-client/app-client-desktop/build.gradle.kts
@@ -0,0 +1,10 @@
+plugins {
+ id("convention.app-jvm")
+ id("convention.compose")
+}
+
+app {
+ jvm {
+ mainClass.set("dev.petuska.kodex.client.MainKt")
+ }
+}
diff --git a/app/app-client/app-client-desktop/src/jvmMain/kotlin/main.kt b/app/app-client/app-client-desktop/src/jvmMain/kotlin/main.kt
new file mode 100644
index 0000000..a411f2f
--- /dev/null
+++ b/app/app-client/app-client-desktop/src/jvmMain/kotlin/main.kt
@@ -0,0 +1,54 @@
+package dev.petuska.kodex.client
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.WindowPosition
+import androidx.compose.ui.window.WindowState
+import androidx.compose.ui.window.application
+import dev.petuska.kodex.client.config.loadEnv
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.view.App
+import dev.petuska.kodex.client.view.AppTheme
+import dev.petuska.kodex.client.view.KodexApp
+import java.awt.Dimension
+import java.awt.Toolkit
+
+private fun getPreferredWindowSize(desiredWidth: Int, desiredHeight: Int): DpSize {
+ val screenSize: Dimension = Toolkit.getDefaultToolkit().screenSize
+ val preferredWidth: Int = (screenSize.width * 0.8f).toInt()
+ val preferredHeight: Int = (screenSize.height * 0.8f).toInt()
+ val width: Int = if (desiredWidth < preferredWidth) desiredWidth else preferredWidth
+ val height: Int = if (desiredHeight < preferredHeight) desiredHeight else preferredHeight
+ return DpSize(width.dp, height.dp)
+}
+
+@Composable
+fun icAppRounded() = painterResource("images/android-chrome-512x512.png")
+
+suspend fun main(vararg args: String) {
+ val env = loadEnv(args = args)
+ application {
+ Window(
+ onCloseRequest = ::exitApplication,
+ title = "Kamp",
+ state = WindowState(
+ position = WindowPosition.Aligned(Alignment.Center),
+ size = getPreferredWindowSize(800, 300)
+ ),
+ undecorated = false,
+ icon = icAppRounded(),
+ ) {
+ KodexApp(env) {
+ dispatch(AppAction.SetDarkTheme(isSystemInDarkTheme()))
+ AppTheme {
+ App()
+ }
+ }
+ }
+ }
+}
diff --git a/app/app-client/app-client-desktop/src/jvmMain/kotlin/view/AppTheme.kt b/app/app-client/app-client-desktop/src/jvmMain/kotlin/view/AppTheme.kt
new file mode 100644
index 0000000..fda015e
--- /dev/null
+++ b/app/app-client/app-client-desktop/src/jvmMain/kotlin/view/AppTheme.kt
@@ -0,0 +1,25 @@
+package dev.petuska.kodex.client.view
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import dev.petuska.kodex.client.util.select
+
+private val DarkThemeColors = darkColorScheme()
+private val LightThemeColors = lightColorScheme()
+
+@Composable
+fun AppTheme(content: @Composable () -> Unit) {
+ val darkTheme by select { darkTheme }
+ val colorScheme = if (darkTheme) {
+ DarkThemeColors
+ } else {
+ LightThemeColors
+ }
+ MaterialTheme(
+ colorScheme = colorScheme,
+ content = content
+ )
+}
diff --git a/app/app-client/app-client-web/build.gradle.kts b/app/app-client/app-client-web/build.gradle.kts
new file mode 100644
index 0000000..ef715d2
--- /dev/null
+++ b/app/app-client/app-client-web/build.gradle.kts
@@ -0,0 +1,34 @@
+plugins {
+ id("convention.app-js")
+ id("convention.compose")
+}
+
+app {
+ js {
+ devServer {
+ proxy = mutableMapOf("/api/*" to "https://kodex.azurewebsites.net")
+ }
+ }
+}
+
+kotlin {
+ sourceSets {
+ jsMain {
+ dependencies {
+ dependencies {
+ implementation(projects.lib.libClient)
+ implementation(libs.kmdc)
+ implementation(libs.compose.routing)
+ implementation(libs.koin.compose)
+ }
+ }
+ }
+ jsTest {
+ dependencies {
+ dependencies {
+ implementation(compose.html.testUtils)
+ }
+ }
+ }
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/main.kt b/app/app-client/app-client-web/src/jsMain/kotlin/main.kt
new file mode 100644
index 0000000..f3f85e9
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/main.kt
@@ -0,0 +1,20 @@
+package dev.petuska.kodex.client
+
+import dev.petuska.kodex.client.config.loadEnv
+import dev.petuska.kodex.client.view.App
+import dev.petuska.kodex.client.view.KodexApp
+import dev.petuska.kodex.client.view.style.AppStyle
+import org.jetbrains.compose.web.css.Style
+import org.jetbrains.compose.web.renderComposable
+
+@JsModule("./style/index.scss")
+private external val Style: dynamic
+
+suspend fun main(vararg args: String) {
+ val env = loadEnv(args = args)
+ Style
+ renderComposable(rootElementId = "root") {
+ Style(AppStyle)
+ KodexApp(env) { App() }
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/util/Routing.kt b/app/app-client/app-client-web/src/jsMain/kotlin/util/Routing.kt
new file mode 100644
index 0000000..9afaf5f
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/util/Routing.kt
@@ -0,0 +1,22 @@
+package dev.petuska.kodex.client.util
+
+import kotlinx.browser.window
+
+object Routing {
+ private fun buildQuery(parameters: Map<*, *>): String = parameters
+ .filterValues { v -> v != null && !(v is String && v.isBlank()) }
+ .entries
+ .joinToString("&", prefix = "?") { (k, v) ->
+ when (v) {
+ is Array<*> -> v.joinToString("&") { "$k=$it" }
+ is Collection<*> -> v.joinToString("&") { "$k=$it" }
+ else -> "$k=$v"
+ }
+ }.takeIf { it != "?" } ?: ""
+
+ fun setQuery(parameters: Map) {
+ window.location.hash = window.location.hash.split("?")[0] + buildQuery(parameters)
+ }
+
+ fun setQuery(vararg parameters: Pair): Unit = setQuery(mapOf(*parameters))
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/util/fontawesome.kt b/app/app-client/app-client-web/src/jsMain/kotlin/util/fontawesome.kt
new file mode 100644
index 0000000..e2144e1
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/util/fontawesome.kt
@@ -0,0 +1,31 @@
+package dev.petuska.kodex.client.util
+
+import androidx.compose.runtime.Composable
+import org.jetbrains.compose.web.dom.I
+
+/**
+ * Regular FontAwesome icons (`fa-regular`)
+ *
+ * [Icon Search](https://fontawesome.com/search?o=r&s=regular&f=classic&m=free)
+ * @param icon name of the icon like `fa-house`
+ */
+@Composable
+fun FarIcon(icon: String) = I({ classes("fa-regular", icon) })
+
+/**
+ * Solid FontAwesome icons (`fa-solid`)
+ *
+ * [Icon Search](https://fontawesome.com/search?o=r&s=solid&f=classic&m=free)
+ * @param icon name of the icon like `fa-house`
+ */
+@Composable
+fun FasIcon(icon: String) = I({ classes("fa-solid", icon) })
+
+/**
+ * Brand FontAwesome icons (`fa-brands`)
+ *
+ * [Icon Search](https://fontawesome.com/search?o=r&f=brands&m=free)
+ * @param icon name of the icon like `fa-github`
+ */
+@Composable
+fun FabIcon(icon: String) = I({ classes("fa-brands", icon) })
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/App.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/App.kt
new file mode 100644
index 0000000..5aac80a
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/App.kt
@@ -0,0 +1,25 @@
+package dev.petuska.kodex.client.view
+
+import androidx.compose.runtime.Composable
+import app.softwork.routingcompose.HashRouter
+import dev.petuska.kmdc.top.app.bar.MDCTopAppBar
+import dev.petuska.kmdc.top.app.bar.MDCTopAppBarType
+import dev.petuska.kmdc.top.app.bar.Main
+import dev.petuska.kmdc.typography.mdcTypography
+import dev.petuska.kodex.client.store.state.Page
+import dev.petuska.kodex.client.view.component.Drawer
+import dev.petuska.kodex.client.view.component.Navbar
+import dev.petuska.kodex.client.view.page.AppRouter
+
+@Composable
+fun App() {
+ HashRouter("/${Page.Home}") {
+ MDCTopAppBar(type = MDCTopAppBarType.Fixed) {
+ Navbar()
+ Main(attrs = { mdcTypography() }) {
+ Drawer()
+ AppRouter()
+ }
+ }
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Drawer.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Drawer.kt
new file mode 100644
index 0000000..0204594
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Drawer.kt
@@ -0,0 +1,73 @@
+package dev.petuska.kodex.client.view.component
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import app.softwork.routingcompose.Router
+import dev.petuska.kmdc.drawer.*
+import dev.petuska.kmdc.list.MDCList
+import dev.petuska.kmdc.list.MDCListSelection
+import dev.petuska.kmdc.list.item.Graphic
+import dev.petuska.kmdc.list.item.ListItem
+import dev.petuska.kmdc.list.item.Text
+import dev.petuska.kmdc.list.onAction
+import dev.petuska.kmdc.typography.mdcTypography
+import dev.petuska.kodex.client.store.AppStore
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.store.state.Page
+import dev.petuska.kodex.client.util.select
+import org.koin.compose.koinInject
+import org.jetbrains.compose.web.dom.Text as CText
+
+@Composable
+fun Drawer() {
+ val open by select { drawerOpen }
+ val store = koinInject()
+
+ MDCDrawer(
+ open = open,
+ type = MDCDrawerType.Modal,
+ attrs = {
+ mdcTypography()
+ onOpened {
+ store.dispatch(AppAction.SetDrawer(true))
+ }
+ onClosed {
+ store.dispatch(AppAction.SetDrawer(false))
+ }
+ }
+ ) {
+ Header {
+ Title("KODEX")
+ Subtitle("Find your stuff")
+ }
+ Content {
+ PageList(pages = Page.values())
+ }
+ }
+}
+
+@Composable
+fun PageList(vararg pages: Page, selectable: Boolean = true) {
+ val current by select { page }
+ val store = koinInject()
+ val router = Router.current
+ MDCList(
+ selection = MDCListSelection.Single,
+ attrs = {
+ onAction {
+ store.dispatch(AppAction.SetDrawer(false))
+ router.navigate("/${pages[it.detail.index]}")
+ }
+ }
+ ) {
+ pages.forEach { page ->
+ ListItem(activated = selectable && page == current) {
+ Graphic(attrs = {
+ classes("material-icons")
+ attr("aria-hidden", "true")
+ }) { CText(page.icon) }
+ Text(page.name)
+ }
+ }
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Layout.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Layout.kt
new file mode 100644
index 0000000..3edd154
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Layout.kt
@@ -0,0 +1,50 @@
+package dev.petuska.kodex.client.view.component
+
+import androidx.compose.runtime.Composable
+import dev.petuska.kmdc.core.AttrsBuilder
+import dev.petuska.kmdc.core.ContentBuilder
+import org.jetbrains.compose.web.css.*
+import org.jetbrains.compose.web.dom.Div
+import org.w3c.dom.HTMLDivElement
+
+object LayoutStyle : StyleSheet() {
+ val flexColumn by style {
+ display(DisplayStyle.Flex)
+ flexDirection(FlexDirection.Column)
+ }
+
+ val flexRow by style {
+ display(DisplayStyle.Flex)
+ flexDirection(FlexDirection.Column)
+ }
+}
+
+@Composable
+fun FlexColumn(
+ attrs: AttrsBuilder? = null,
+ content: ContentBuilder
+) {
+ Div(
+ attrs = {
+ classes(LayoutStyle.flexColumn)
+ attrs?.invoke(this)
+ }
+ ) {
+ content()
+ }
+}
+
+@Composable
+fun FlexRow(
+ attrs: AttrsBuilder? = null,
+ content: ContentBuilder
+) {
+ Div(
+ attrs = {
+ classes(LayoutStyle.flexRow)
+ attrs?.invoke(this)
+ }
+ ) {
+ content()
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Navbar.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Navbar.kt
new file mode 100644
index 0000000..7df4b85
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/component/Navbar.kt
@@ -0,0 +1,130 @@
+package dev.petuska.kodex.client.view.component
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import app.softwork.routingcompose.Router
+import dev.petuska.kmdc.core.AttrsBuilder
+import dev.petuska.kmdc.linear.progress.MDCLinearProgress
+import dev.petuska.kmdc.top.app.bar.*
+import dev.petuska.kmdc.typography.mdcTypography
+import dev.petuska.kodex.client.store.AppStore
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.store.state.Page
+import dev.petuska.kodex.client.util.FabIcon
+import dev.petuska.kodex.client.util.select
+import dev.petuska.kodex.client.view.style.AppStyle
+import org.jetbrains.compose.web.attributes.ATarget
+import org.jetbrains.compose.web.attributes.href
+import org.jetbrains.compose.web.attributes.target
+import org.jetbrains.compose.web.css.*
+import org.jetbrains.compose.web.dom.Img
+import org.jetbrains.compose.web.dom.Text
+import org.koin.compose.koinInject
+import org.w3c.dom.HTMLImageElement
+
+object NavbarStyle : StyleSheet(AppStyle) {
+ val container by style {
+ top(0.px)
+ left(0.px)
+ right(0.px)
+ }
+ val brand by style {
+ cursor("pointer")
+ hover(self) style {
+ child(self, universal) style {
+ textDecoration("none")
+ }
+ }
+ }
+
+ val logoSection by style {
+ display(DisplayStyle.Flex)
+ flexDirection(FlexDirection.Column)
+ }
+ val logo by style {
+ width(3.cssRem)
+ height(3.cssRem)
+ display(DisplayStyle.Inline)
+ borderRadius(50.percent)
+ }
+}
+
+@Composable
+fun MDCTopAppBarContextScope.Navbar() {
+ val store = koinInject()
+ val drawerOpen by select { drawerOpen }
+ val page by select { page }
+ TopAppBar(
+ attrs = {
+ classes(NavbarStyle.container)
+ mdcTypography()
+ }
+ ) {
+ Row {
+ Section(align = MDCTopAppBarSectionAlign.Start) {
+ NavButton(attrs = {
+ classes("material-icons")
+ onClick {
+ store.dispatch(AppAction.ToggleDrawer)
+ }
+ }) {
+ Text(if (drawerOpen) "close" else "menu")
+ }
+ val title = remember(page) {
+ "KODEX" + if (page != Page.Home) " | ${page.name.uppercase()}" else ""
+ }
+ Title(title)
+ // KodexIcon()
+ }
+ Section(align = MDCTopAppBarSectionAlign.End) {
+ val router = Router.current
+ CountBadge()
+ ActionButton(attrs = {
+ classes("material-icons")
+ onClick { router.navigate("/${Page.Home}") }
+ }) {
+ Text("home")
+ }
+ ActionLink(
+ attrs = {
+ href("https://github.com/mpetuska/kodex")
+ target(ATarget.Blank)
+ classes(AppStyle.fixFabContainer)
+ }
+ ) {
+ FabIcon("fa-github")
+ }
+ }
+ }
+ ProgressBar()
+ }
+}
+
+@Composable
+private fun ProgressBar() {
+ val loading by select { loading }
+ val progress by select { progress }
+ MDCLinearProgress(
+ determinate = loading && progress != null,
+ closed = !loading,
+ progress = progress ?: 0,
+ )
+}
+
+@Composable
+private fun CountBadge() {
+ Text("TODO")
+}
+
+@Composable
+@Suppress("UnusedPrivateMember")
+private fun KodexIcon(attrs: AttrsBuilder? = null) {
+ Img(
+ src = "./images/kodex.svg",
+ attrs = {
+ classes(NavbarStyle.logo)
+ attrs?.invoke(this)
+ },
+ )
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/page/AppRouter.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/AppRouter.kt
new file mode 100644
index 0000000..a551bf7
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/AppRouter.kt
@@ -0,0 +1,38 @@
+package dev.petuska.kodex.client.view.page
+
+import androidx.compose.runtime.Composable
+import app.softwork.routingcompose.RouteBuilder
+import dev.petuska.kmdc.typography.MDCBody1
+import dev.petuska.kodex.client.store.AppStore
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.store.state.Page
+import dev.petuska.kodex.client.store.thunk.parseQuery
+import dev.petuska.kodex.client.view.page.home.HomePage
+import dev.petuska.kodex.client.view.page.search.SearchPage
+import dev.petuska.kodex.client.view.page.statistics.StatisticsPage
+import org.koin.compose.koinInject
+
+@Composable
+fun RouteBuilder.AppRouter() {
+ val store = koinInject()
+ string { hash ->
+ val chunks = hash.split("?")
+ val route = chunks[0]
+ val rootRoute = route.split("/")[0]
+ chunks.getOrNull(1)?.also {
+ store.dispatch(parseQuery(it))
+ }
+
+ val page = Page.values().find { it.route == rootRoute }?.also {
+ store.dispatch(AppAction.SetPage(it))
+ }
+ when (page) {
+ Page.Home -> HomePage()
+ Page.Search -> SearchPage()
+ Page.Statistics -> StatisticsPage()
+ Page.Random -> MDCBody1("Random")
+ null -> println("null")
+// null -> router.navigate("/${Page.Home}")
+ }
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/page/home/HomePage.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/home/HomePage.kt
new file mode 100644
index 0000000..79aeaca
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/home/HomePage.kt
@@ -0,0 +1,12 @@
+package dev.petuska.kodex.client.view.page.home
+
+import androidx.compose.runtime.Composable
+import dev.petuska.kmdc.typography.MDCBody1
+import dev.petuska.kodex.client.store.state.Page
+import dev.petuska.kodex.client.view.component.PageList
+
+@Composable
+fun HomePage() {
+ MDCBody1("Home")
+ PageList(pages = Page.values())
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/page/search/SearchForm.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/search/SearchForm.kt
new file mode 100644
index 0000000..01a75bf
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/search/SearchForm.kt
@@ -0,0 +1,153 @@
+package dev.petuska.kodex.client.view.page.search
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import dev.petuska.kmdc.button.Icon
+import dev.petuska.kmdc.button.Label
+import dev.petuska.kmdc.button.MDCButton
+import dev.petuska.kmdc.button.MDCButtonType
+import dev.petuska.kmdc.checkbox.MDCCheckbox
+import dev.petuska.kmdc.form.field.MDCFormField
+import dev.petuska.kmdc.layout.grid.Cell
+import dev.petuska.kmdc.layout.grid.Cells
+import dev.petuska.kmdc.layout.grid.MDCLayoutGridScope
+import dev.petuska.kmdc.textfield.MDCTextField
+import dev.petuska.kmdc.textfield.MDCTextFieldType
+import dev.petuska.kmdc.typography.MDCOverline
+import dev.petuska.kodex.client.service.LibraryService
+import dev.petuska.kodex.client.store.AppStore
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.store.thunk.fetchLibraryPage
+import dev.petuska.kodex.client.util.Routing
+import dev.petuska.kodex.client.util.select
+import dev.petuska.kodex.client.view.style.AppStyle
+import dev.petuska.kodex.core.domain.KotlinTarget
+import org.jetbrains.compose.web.dom.Text
+import org.koin.compose.koinInject
+
+@Composable
+fun MDCLayoutGridScope.SearchForm() {
+ Cells {
+ Cell(span = 12u) {
+ TextFilter()
+ }
+ Cell(span = 12u) {
+ TargetsFilter()
+ }
+ }
+}
+
+@Composable
+private fun MDCLayoutGridScope.TargetsFilter() {
+ Cells {
+ Cell(span = 12u, attrs = { classes(AppStyle.centered) }) {
+ MDCOverline("Targets Filter")
+ }
+ val targetGroups = remember {
+ mapOf(
+ KotlinTarget.Common.category to setOf(KotlinTarget.Common),
+ KotlinTarget.JVM.category to KotlinTarget.JVM.values(),
+ KotlinTarget.JS.category to KotlinTarget.JS.values(),
+ ) + KotlinTarget.Native.values().groupBy { it.family }.map { (k, v) -> k to v.toSet() }
+ }
+ targetGroups.forEach { (category, targets) ->
+ Cell {
+ TargetGroup(category, targets)
+ }
+ }
+ }
+}
+
+@Composable
+private fun TargetGroup(
+ category: String,
+ groupTargets: Set
+) {
+ val store = koinInject()
+ val selectedTargets by select { (targets ?: setOf()).filter { it in groupTargets } }
+ val (allSelected, noneSelected) = remember(selectedTargets) {
+ selectedTargets.containsAll(groupTargets) to groupTargets.none { it in selectedTargets }
+ }
+ MDCFormField {
+ MDCCheckbox(
+ checked = allSelected,
+ label = category,
+ indeterminate = !allSelected && !noneSelected,
+ attrs = {
+ onInput {
+ if (it.value) {
+ store.dispatch(AppAction.AddTargets(groupTargets))
+ } else {
+ store.dispatch(AppAction.RemoveTargets(groupTargets))
+ }
+ }
+ }
+ )
+ }
+ groupTargets.forEach { target ->
+ MDCFormField {
+ MDCCheckbox(
+ checked = target in selectedTargets,
+ label = target.toString(),
+ attrs = {
+ onInput {
+ if (it.value) {
+ store.dispatch(AppAction.AddTargets(target))
+ } else {
+ store.dispatch(AppAction.RemoveTargets(target))
+ }
+ }
+ }
+ )
+ }
+ }
+}
+
+@Composable
+private fun MDCLayoutGridScope.TextFilter() {
+ val store = koinInject()
+ val search by select { search }
+ val targets by select { targets }
+ val libraryService = koinInject()
+
+ val submit: () -> Unit = remember(search, targets) {
+ {
+ store.dispatch(
+ libraryService.fetchLibraryPage(
+ page = 1,
+ size = 50,
+ search = search,
+ targets = targets,
+ )
+ )
+ Routing.setQuery(mapOf("search" to search, "target" to targets?.map { it.platform }))
+ }
+ }
+ Cells(attrs = { classes(AppStyle.centered) }) {
+ Cell(span = 6u, attrs = { classes(AppStyle.centeredRight) }) {
+ MDCTextField(
+ value = search ?: "",
+ label = "Search by text",
+ type = MDCTextFieldType.Outlined,
+ attrs = {
+ onKeyDown {
+ if (it.key == "Enter") submit()
+ }
+ onInput { store.dispatch(AppAction.SetSearch(it.value.takeIf(String::isNotBlank))) }
+ }
+ )
+ }
+ Cell(span = 6u, attrs = { classes(AppStyle.centeredLeft) }) {
+ MDCButton(
+ type = MDCButtonType.Raised,
+ attrs = {
+ onClick { submit() }
+ }
+ ) {
+ Icon(attrs = { classes("material-icons") }) { Text("search") }
+ Label("Search")
+ }
+ }
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/page/search/SearchPage.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/search/SearchPage.kt
new file mode 100644
index 0000000..a79279d
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/search/SearchPage.kt
@@ -0,0 +1,74 @@
+package dev.petuska.kodex.client.view.page.search
+
+import androidx.compose.runtime.Composable
+import dev.petuska.kmdc.button.Label
+import dev.petuska.kmdc.card.*
+import dev.petuska.kmdc.layout.grid.Cell
+import dev.petuska.kmdc.layout.grid.Cells
+import dev.petuska.kmdc.layout.grid.MDCLayoutGrid
+import dev.petuska.kmdc.typography.MDCH2
+import dev.petuska.kmdc.typography.MDCH4
+import dev.petuska.kmdc.typography.MDCOverline
+import dev.petuska.kodex.client.view.style.AppStyle
+import org.jetbrains.compose.web.css.backgroundImage
+import org.jetbrains.compose.web.dom.Text
+
+@Composable
+fun SearchPage() {
+ MDCLayoutGrid {
+ Cells {
+ Cell(span = 12u, attrs = { classes(AppStyle.centered) }) {
+ MDCH4("Library Search")
+ MDCOverline("Search and filter kotlin multiplatform libraries")
+ }
+ Cell(span = 12u) {
+ SearchForm()
+ }
+ Cell(span = 12u) {
+ Cells {
+ repeat(7) {
+ Cell {
+ SampleCard()
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun SampleCard() {
+ MDCCard {
+ PrimaryAction {
+ Media(
+ type = MDCCardMediaType.Cinema,
+ attrs = {
+ style {
+ backgroundImage("url('/images/kodex.svg')")
+ }
+ }
+ ) {
+ MediaContent { MDCH2("Title 1") }
+ }
+ }
+ Actions {
+ ActionButtons {
+ ActionButton {
+ Label("Action 1")
+ }
+ ActionButton {
+ Label("Action 2")
+ }
+ }
+ ActionIcons {
+ ActionIconButton(attrs = { classes("material-icons") }) {
+ Text("star")
+ }
+ ActionIconButton(attrs = { classes("material-icons") }) {
+ Text("star")
+ }
+ }
+ }
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/page/statistics/StatisticsPage.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/statistics/StatisticsPage.kt
new file mode 100644
index 0000000..18ff057
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/page/statistics/StatisticsPage.kt
@@ -0,0 +1,9 @@
+package dev.petuska.kodex.client.view.page.statistics
+
+import androidx.compose.runtime.Composable
+import dev.petuska.kmdc.typography.MDCBody1
+
+@Composable
+fun StatisticsPage() {
+ MDCBody1("Statistics")
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/style/AppStyle.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/style/AppStyle.kt
new file mode 100644
index 0000000..b0c35c2
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/style/AppStyle.kt
@@ -0,0 +1,27 @@
+package dev.petuska.kodex.client.view.style
+
+import org.jetbrains.compose.web.css.*
+
+object AppStyle : StyleSheet() {
+ val fixFabContainer by style {
+ display(DisplayStyle.Flex)
+ }
+
+ val centered by style {
+ display(DisplayStyle.Grid)
+ alignItems(AlignItems.Center)
+ justifyItems("center")
+ }
+
+ val centeredLeft by style {
+ display(DisplayStyle.Grid)
+ alignItems(AlignItems.Center)
+ justifyItems("left")
+ }
+
+ val centeredRight by style {
+ display(DisplayStyle.Grid)
+ alignItems(AlignItems.Center)
+ justifyItems("right")
+ }
+}
diff --git a/app/app-client/app-client-web/src/jsMain/kotlin/view/style/Colours.kt b/app/app-client/app-client-web/src/jsMain/kotlin/view/style/Colours.kt
new file mode 100644
index 0000000..acbf0a4
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/kotlin/view/style/Colours.kt
@@ -0,0 +1,37 @@
+package dev.petuska.kodex.client.view.style
+
+import org.jetbrains.compose.web.css.CSSColorValue
+import org.jetbrains.compose.web.css.Color
+
+interface Colours {
+ val primary: CSSColorValue
+ val secondary: CSSColorValue
+ val success: CSSColorValue
+ val danger: CSSColorValue
+ val warning: CSSColorValue
+ val info: CSSColorValue
+ val light: CSSColorValue
+ val dark: CSSColorValue
+}
+
+object LightColours : Colours {
+ override val primary: CSSColorValue = Color("#2B3F4F")
+ override val secondary: CSSColorValue = Color("#95A5A6")
+ override val success: CSSColorValue = Color("#00BE9D")
+ override val danger: CSSColorValue = Color("#E94241")
+ override val warning: CSSColorValue = Color("#F4982F")
+ override val info: CSSColorValue = Color("#2E9BD8")
+ override val light: CSSColorValue = Color("#ECF0F1")
+ override val dark: CSSColorValue = Color("#7B8A8B")
+}
+
+object DarkColours : Colours {
+ override val primary: CSSColorValue = Color("#365B7D")
+ override val secondary: CSSColorValue = Color("#444444")
+ override val success: CSSColorValue = Color("#00BD8E")
+ override val danger: CSSColorValue = Color("#E94241")
+ override val warning: CSSColorValue = Color("#F4982F")
+ override val info: CSSColorValue = Color("#2E9BD8")
+ override val light: CSSColorValue = Color("#ADB5BD")
+ override val dark: CSSColorValue = Color("#303030")
+}
diff --git a/app/src/jsMain/resources/application.env b/app/app-client/app-client-web/src/jsMain/resources/application.env
similarity index 100%
rename from app/src/jsMain/resources/application.env
rename to app/app-client/app-client-web/src/jsMain/resources/application.env
diff --git a/app/src/jsMain/resources/browserconfig.xml b/app/app-client/app-client-web/src/jsMain/resources/browserconfig.xml
similarity index 73%
rename from app/src/jsMain/resources/browserconfig.xml
rename to app/app-client/app-client-web/src/jsMain/resources/browserconfig.xml
index 74bb89a..60b6e60 100644
--- a/app/src/jsMain/resources/browserconfig.xml
+++ b/app/app-client/app-client-web/src/jsMain/resources/browserconfig.xml
@@ -2,7 +2,7 @@
-
+
#da532c
diff --git a/app/src/jsMain/resources/android-chrome-192x192.png b/app/app-client/app-client-web/src/jsMain/resources/images/android-chrome-192x192.png
similarity index 100%
rename from app/src/jsMain/resources/android-chrome-192x192.png
rename to app/app-client/app-client-web/src/jsMain/resources/images/android-chrome-192x192.png
diff --git a/app/src/jsMain/resources/android-chrome-512x512.png b/app/app-client/app-client-web/src/jsMain/resources/images/android-chrome-512x512.png
similarity index 100%
rename from app/src/jsMain/resources/android-chrome-512x512.png
rename to app/app-client/app-client-web/src/jsMain/resources/images/android-chrome-512x512.png
diff --git a/app/src/jsMain/resources/apple-touch-icon.png b/app/app-client/app-client-web/src/jsMain/resources/images/apple-touch-icon.png
similarity index 100%
rename from app/src/jsMain/resources/apple-touch-icon.png
rename to app/app-client/app-client-web/src/jsMain/resources/images/apple-touch-icon.png
diff --git a/app/src/jsMain/resources/favicon-16x16.png b/app/app-client/app-client-web/src/jsMain/resources/images/favicon-16x16.png
similarity index 100%
rename from app/src/jsMain/resources/favicon-16x16.png
rename to app/app-client/app-client-web/src/jsMain/resources/images/favicon-16x16.png
diff --git a/app/src/jsMain/resources/favicon-32x32.png b/app/app-client/app-client-web/src/jsMain/resources/images/favicon-32x32.png
similarity index 100%
rename from app/src/jsMain/resources/favicon-32x32.png
rename to app/app-client/app-client-web/src/jsMain/resources/images/favicon-32x32.png
diff --git a/app/src/jsMain/resources/favicon.ico b/app/app-client/app-client-web/src/jsMain/resources/images/favicon.ico
similarity index 100%
rename from app/src/jsMain/resources/favicon.ico
rename to app/app-client/app-client-web/src/jsMain/resources/images/favicon.ico
diff --git a/app/src/jsMain/resources/kamp.ico b/app/app-client/app-client-web/src/jsMain/resources/images/kamp.ico
similarity index 100%
rename from app/src/jsMain/resources/kamp.ico
rename to app/app-client/app-client-web/src/jsMain/resources/images/kamp.ico
diff --git a/app/src/jsMain/resources/kamp.svg b/app/app-client/app-client-web/src/jsMain/resources/images/kamp.svg
similarity index 100%
rename from app/src/jsMain/resources/kamp.svg
rename to app/app-client/app-client-web/src/jsMain/resources/images/kamp.svg
diff --git a/app/src/jsMain/resources/mstile-150x150.png b/app/app-client/app-client-web/src/jsMain/resources/images/mstile-150x150.png
similarity index 100%
rename from app/src/jsMain/resources/mstile-150x150.png
rename to app/app-client/app-client-web/src/jsMain/resources/images/mstile-150x150.png
diff --git a/app/src/jsMain/resources/safari-pinned-tab.svg b/app/app-client/app-client-web/src/jsMain/resources/images/safari-pinned-tab.svg
similarity index 100%
rename from app/src/jsMain/resources/safari-pinned-tab.svg
rename to app/app-client/app-client-web/src/jsMain/resources/images/safari-pinned-tab.svg
diff --git a/app/app-client/app-client-web/src/jsMain/resources/index.html b/app/app-client/app-client-web/src/jsMain/resources/index.html
new file mode 100644
index 0000000..7f4bd3e
--- /dev/null
+++ b/app/app-client/app-client-web/src/jsMain/resources/index.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ KODEX
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/jsMain/resources/site.webmanifest b/app/app-client/app-client-web/src/jsMain/resources/site.webmanifest
similarity index 64%
rename from app/src/jsMain/resources/site.webmanifest
rename to app/app-client/app-client-web/src/jsMain/resources/site.webmanifest
index 5d39c0d..c331c27 100644
--- a/app/src/jsMain/resources/site.webmanifest
+++ b/app/app-client/app-client-web/src/jsMain/resources/site.webmanifest
@@ -1,14 +1,14 @@
{
- "name": "KAMP",
- "short_name": "KAMP",
+ "name": "Kodex",
+ "short_name": "Kodex",
"icons": [
{
- "src": "/android-chrome-192x192.png",
+ "src": "/images/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
- "src": "/android-chrome-512x512.png",
+ "src": "/images/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
diff --git a/app/app-client/app-client-web/src/jsMain/resources/style/index.scss b/app/app-client/app-client-web/src/jsMain/resources/style/index.scss
new file mode 100644
index 0000000..e69de29
diff --git a/app/Dockerfile b/app/app-server/Dockerfile
similarity index 62%
rename from app/Dockerfile
rename to app/app-server/Dockerfile
index 73804e6..6e61093 100644
--- a/app/Dockerfile
+++ b/app/app-server/Dockerfile
@@ -1,7 +1,7 @@
FROM gcr.io/distroless/java:11
WORKDIR /app
-COPY build/libs/app-jvm*.jar app.jar
-ENV PORT=8000
+COPY build/libs/server-jvm-*-fat.jar app.jar
+ENV PORT=8080
EXPOSE $PORT
ENTRYPOINT ["java", "-jar", "app.jar"]
diff --git a/app/app-server/build.gradle.kts b/app/app-server/build.gradle.kts
new file mode 100644
index 0000000..650fa0e
--- /dev/null
+++ b/app/app-server/build.gradle.kts
@@ -0,0 +1,53 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+
+plugins {
+ id("convention.app-jvm")
+}
+
+app {
+ jvm {
+ mainClass.set("dev.petuska.kodex.server.MainKt")
+ }
+}
+
+kotlin {
+ sourceSets {
+ jvmMain {
+ dependencies {
+ implementation(projects.lib.libCore)
+ implementation(projects.lib.libRepository)
+
+ implementation(libs.ktor.server.cio)
+ implementation(libs.ktor.server.auth)
+ implementation(libs.ktor.server.call.logging)
+ implementation(libs.ktor.server.default.headers)
+ implementation(libs.ktor.server.caching.headers)
+ implementation(libs.ktor.server.status.pages)
+ implementation(libs.ktor.server.compression)
+ implementation(libs.ktor.server.content.negotiation)
+ implementation(libs.ktor.serialization.kotlinx.json)
+ implementation(libs.ktor.serialization.kotlinx.cbor)
+
+ implementation(libs.koin.ktor)
+ implementation(libs.koin.logger.slf4j)
+ implementation(libs.logback.classic)
+ }
+ }
+ }
+}
+
+tasks {
+ val jsBrowserDistribution = findByPath(":app:app-client:app-client-web:jsBrowserDistribution")
+ named("jvmFatJar") {
+ dependsOn(jsBrowserDistribution)
+ into("WEB-INF") {
+ from(jsBrowserDistribution)
+ exclude("**/*.scss")
+ }
+ inputs.files(jsBrowserDistribution)
+ }
+ named("jvmRun") {
+ classpath(project(":app:app-client:app-client-web").buildDir.resolve("dist"))
+ systemProperty("io.ktor.development", "true")
+ }
+}
diff --git a/app/app-server/http-client.env.json b/app/app-server/http-client.env.json
new file mode 100644
index 0000000..f0cda83
--- /dev/null
+++ b/app/app-server/http-client.env.json
@@ -0,0 +1,8 @@
+{
+ "localhost": {
+ "host": "http://localhost:8080"
+ },
+ "live": {
+ "host": "https://kodex.azurewebsites.net"
+ }
+}
diff --git a/app/app-server/http-client.http b/app/app-server/http-client.http
new file mode 100644
index 0000000..95de675
--- /dev/null
+++ b/app/app-server/http-client.http
@@ -0,0 +1,14 @@
+###
+GET {{host}}/api/libraries?target=android_x64
+
+###
+GET {{host}}/api/libraries/count
+
+###
+GET {{host}}/api/statistics/count
+
+###
+GET {{host}}/api/statistics
+
+###
+GET {{host}}/api/status
diff --git a/app/app-server/src/jvmMain/kotlin/config/diConfig.kt b/app/app-server/src/jvmMain/kotlin/config/diConfig.kt
new file mode 100644
index 0000000..3b3494b
--- /dev/null
+++ b/app/app-server/src/jvmMain/kotlin/config/diConfig.kt
@@ -0,0 +1,16 @@
+package dev.petuska.kodex.server.config
+
+import dev.petuska.kodex.core.config.serialisationModule
+import dev.petuska.kodex.repository.config.repositoryModule
+import dev.petuska.kodex.server.util.PrivateEnv
+import io.ktor.server.application.*
+import org.koin.ktor.plugin.Koin
+import org.koin.logger.slf4jLogger
+
+fun Application.diConfig() = install(Koin) {
+ slf4jLogger()
+ modules(
+ serialisationModule,
+ repositoryModule(PrivateEnv.MONGO_STRING, PrivateEnv.MONGO_DATABASE)
+ )
+}
diff --git a/app/app-server/src/jvmMain/kotlin/config/features.kt b/app/app-server/src/jvmMain/kotlin/config/features.kt
new file mode 100644
index 0000000..50cbdf6
--- /dev/null
+++ b/app/app-server/src/jvmMain/kotlin/config/features.kt
@@ -0,0 +1,43 @@
+package dev.petuska.kodex.server.config
+
+import dev.petuska.kodex.server.util.PrivateEnv
+import dev.petuska.kodex.server.util.PublicEnv
+import io.ktor.serialization.kotlinx.cbor.*
+import io.ktor.serialization.kotlinx.json.*
+import io.ktor.server.application.*
+import io.ktor.server.auth.*
+import io.ktor.server.plugins.cachingheaders.*
+import io.ktor.server.plugins.callloging.*
+import io.ktor.server.plugins.compression.*
+import io.ktor.server.plugins.contentnegotiation.*
+import io.ktor.server.plugins.defaultheaders.*
+import io.ktor.server.plugins.statuspages.*
+import kotlinx.serialization.ExperimentalSerializationApi
+import org.koin.ktor.ext.get
+
+fun Application.features() {
+ diConfig()
+ install(CallLogging) { level = PublicEnv.LOG_LEVEL }
+ install(ContentNegotiation) {
+ json(this@features.get())
+ @OptIn(ExperimentalSerializationApi::class)
+ cbor(this@features.get())
+ }
+ install(Compression)
+ install(DefaultHeaders)
+ install(CachingHeaders)
+ install(StatusPages)
+ install(Authentication) {
+ basic {
+ validate { credentials ->
+ if (credentials.name == PrivateEnv.ADMIN_USER &&
+ credentials.password == PrivateEnv.ADMIN_PASSWORD
+ ) {
+ UserIdPrincipal(credentials.name)
+ } else {
+ null
+ }
+ }
+ }
+ }
+}
diff --git a/app/app-server/src/jvmMain/kotlin/config/routing.kt b/app/app-server/src/jvmMain/kotlin/config/routing.kt
new file mode 100644
index 0000000..1ce6d52
--- /dev/null
+++ b/app/app-server/src/jvmMain/kotlin/config/routing.kt
@@ -0,0 +1,74 @@
+package dev.petuska.kodex.server.config
+
+import dev.petuska.kodex.repository.LibraryRepository
+import dev.petuska.kodex.repository.StatisticRepository
+import dev.petuska.kodex.server.util.*
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.http.content.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import org.koin.ktor.ext.inject
+import java.io.File
+
+fun Application.routing() = routing {
+ api()
+ staticContent()
+}
+
+private fun Route.api() = route("/api") {
+ get("/status") { call.respond(HttpStatusCode.OK) }
+ libraries()
+ statistics()
+}
+
+private fun Route.libraries() = route("/libraries") {
+ val repo by inject()
+ get {
+ call.respond(
+ repo.search(
+ page = call.request.page,
+ size = call.request.pageSize,
+ search = call.request.search,
+ targets = call.request.targets
+ ).let(call.request::paginateResponse)
+ )
+ }
+ get("/count") {
+ call.respond(
+ repo.count(
+ search = call.request.search,
+ targets = call.request.targets
+ )
+ )
+ }
+}
+
+private fun Route.statistics() = route("/statistics") {
+ val repo by inject()
+ get {
+ call.respond(
+ repo.search(
+ page = call.request.page,
+ size = call.request.pageSize,
+ from = call.request.from,
+ to = call.request.to
+ ).let(call.request::paginateResponse)
+ )
+ }
+ get("/count") {
+ call.respond(
+ repo.count(
+ from = call.request.from,
+ to = call.request.to
+ )
+ )
+ }
+}
+
+private fun Routing.staticContent() {
+ get("/application.env") { call.respondText("$PublicEnv") }
+ val folder = "WEB-INF"
+ staticFiles("/", File(folder))
+ staticResources("/", folder)
+}
diff --git a/app/app-server/src/jvmMain/kotlin/main.kt b/app/app-server/src/jvmMain/kotlin/main.kt
new file mode 100644
index 0000000..b4e6c3c
--- /dev/null
+++ b/app/app-server/src/jvmMain/kotlin/main.kt
@@ -0,0 +1,19 @@
+package dev.petuska.kodex.server
+
+import dev.petuska.kodex.server.config.features
+import dev.petuska.kodex.server.config.routing
+import dev.petuska.kodex.server.util.PublicEnv
+import io.ktor.server.application.*
+import io.ktor.server.cio.*
+
+fun main(args: Array) {
+ System.setProperty("jdk.tls.client.protocols", "TLSv1.2")
+ EngineMain.main(args)
+}
+
+@Suppress("unused")
+fun Application.module() {
+ features()
+ routing()
+ log.info("ENV: $PublicEnv")
+}
diff --git a/app/src/jvmMain/kotlin/app/util/env.kt b/app/app-server/src/jvmMain/kotlin/util/env.kt
similarity index 51%
rename from app/src/jvmMain/kotlin/app/util/env.kt
rename to app/app-server/src/jvmMain/kotlin/util/env.kt
index dd64881..79f9d1a 100644
--- a/app/src/jvmMain/kotlin/app/util/env.kt
+++ b/app/app-server/src/jvmMain/kotlin/util/env.kt
@@ -1,16 +1,19 @@
-package app.util
+package dev.petuska.kodex.server.util
-import kamp.util.Env
+import dev.petuska.kodex.core.util.Env
+import org.slf4j.event.Level
object PrivateEnv : Env() {
val MONGO_STRING by EnvDelegate { it ?: "mongodb://localhost:27017" }
- val MONGO_DATABASE by EnvDelegate { it ?: "kamp" }
+ val MONGO_DATABASE by EnvDelegate { it ?: "kodex" }
val ADMIN_USER by EnvDelegate { requireNotNull(it) }
val ADMIN_PASSWORD by EnvDelegate { requireNotNull(it) }
}
object PublicEnv : Env() {
+ val LOG_LEVEL by EnvDelegate { it?.uppercase()?.let(Level::valueOf) ?: Level.INFO }
+ val PORT by EnvDelegate { it?.toInt() ?: 8080 }
val API_URL by EnvDelegate {
- it ?: findEnv("WEBSITE_HOSTNAME")?.let { host -> "https://$host" } ?: "http://localhost:8080"
+ it ?: findEnv("WEBSITE_HOSTNAME")?.let { host -> "https://$host" } ?: "http://localhost:$PORT"
}
}
diff --git a/app/app-server/src/jvmMain/kotlin/util/paging.kt b/app/app-server/src/jvmMain/kotlin/util/paging.kt
new file mode 100644
index 0000000..8e38aab
--- /dev/null
+++ b/app/app-server/src/jvmMain/kotlin/util/paging.kt
@@ -0,0 +1,52 @@
+package dev.petuska.kodex.server.util
+
+import dev.petuska.kodex.core.domain.PagedResponse
+import dev.petuska.kodex.core.util.buildIf
+import io.ktor.http.URLBuilder
+import io.ktor.server.request.ApplicationRequest
+import io.ktor.server.request.port
+import io.ktor.server.request.uri
+
+fun ApplicationRequest.buildNextUrl(currentElementCount: Int): String? =
+ buildIf(currentElementCount == pageSize) {
+ URLBuilder(call.request.uri)
+ .apply {
+ port = call.request.port()
+ parameters["page"] = "${page + 1}"
+ parameters["size"] = "$pageSize"
+ }.buildString()
+ }
+
+fun ApplicationRequest.buildPrevUrl(): String? = buildIf(page > 1) {
+ URLBuilder(call.request.uri)
+ .apply {
+ port = call.request.port()
+ parameters["page"] = "${page - 1}"
+ parameters["size"] = "$pageSize"
+ }.buildString()
+}
+
+fun ApplicationRequest.paginateResponse(data: List) = PagedResponse(
+ data = data,
+ prev = buildPrevUrl(),
+ page = page,
+ next = buildNextUrl(data.size)
+)
+
+val ApplicationRequest.page
+ get() = queryParameters["page"]?.let(String::toIntOrNull) ?: 1
+
+val ApplicationRequest.pageSize
+ get() = queryParameters["size"]?.let(String::toIntOrNull) ?: 50
+
+val ApplicationRequest.search
+ get() = queryParameters["search"]?.takeIf { it.isNotBlank() }
+
+val ApplicationRequest.targets
+ get() = queryParameters.getAll("target")?.toSet()?.takeIf { it.isNotEmpty() }
+
+val ApplicationRequest.from
+ get() = queryParameters["from"]?.let(String::toLongOrNull)
+
+val ApplicationRequest.to
+ get() = queryParameters["to"]?.let(String::toLongOrNull)
diff --git a/app/src/jvmMain/resources/ApplicationInsights.xml b/app/app-server/src/jvmMain/resources/ApplicationInsights.xml
similarity index 100%
rename from app/src/jvmMain/resources/ApplicationInsights.xml
rename to app/app-server/src/jvmMain/resources/ApplicationInsights.xml
diff --git a/app/src/jvmMain/resources/application.conf b/app/app-server/src/jvmMain/resources/application.conf
similarity index 53%
rename from app/src/jvmMain/resources/application.conf
rename to app/app-server/src/jvmMain/resources/application.conf
index db42cde..518f8fd 100644
--- a/app/src/jvmMain/resources/application.conf
+++ b/app/app-server/src/jvmMain/resources/application.conf
@@ -5,13 +5,13 @@ ktor {
watch = [
build/classes/kotlin/jvm/main,
build/classes/kotlin/jvm/test,
- build/processedResources/kotlin/jvm/main,
- build/processedResources/kotlin/jvm/test,
- build/dist/WEB-INF
+ build/processedResources/jvm/main,
+ build/processedResources/jvm/test,
+ build/dist/js
]
}
application {
- modules = [app.IndexKt.module]
+ modules = [dev.petuska.kodex.server.MainKt.module]
}
}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
deleted file mode 100644
index 81dbe67..0000000
--- a/app/build.gradle.kts
+++ /dev/null
@@ -1,134 +0,0 @@
-plugins {
- kotlin("multiplatform")
- kotlin("plugin.serialization")
- id("org.jlleitschuh.gradle.ktlint")
-}
-
-repositories {
- mavenCentral()
-}
-tasks {
- withType {
- useJUnitPlatform()
- }
- withType {
- kotlinOptions {
- jvmTarget = "${JavaVersion.VERSION_11}"
- }
- }
-}
-
-val mainClassName = "app.IndexKt"
-val jsOutputFile = "kamp-$version.js"
-
-kotlin {
- jvm()
- js {
- useCommonJs()
- binaries.executable()
- browser {
- distribution {
- directory = buildDir.resolve("dist/WEB-INF")
- }
- commonWebpackConfig {
- cssSupport.enabled = true
- outputFileName = jsOutputFile
- devServer = devServer?.copy(
- port = 3000,
- proxy = mutableMapOf("/api/*" to "http://localhost:8080"),
- open = false
- )
- }
- }
- }
- sourceSets {
- named("commonMain") {
- dependencies {
- implementation(project(":common"))
- implementation("org.kodein.di:kodein-di:_")
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:_")
- }
- }
- named("jvmMain") {
- dependencies {
- implementation("io.ktor:ktor-server-cio:_")
- implementation("io.ktor:ktor-serialization:_")
- implementation("io.ktor:ktor-auth:_")
- implementation("ch.qos.logback:logback-classic:_")
- implementation("org.kodein.di:kodein-di-framework-ktor-server-jvm:_")
- implementation("com.microsoft.azure:applicationinsights-web-auto:_")
- implementation("org.litote.kmongo:kmongo-coroutine-serialization:_")
- }
- }
- named("jsMain") {
- dependencies {
- implementation("io.ktor:ktor-client-serialization:_")
- implementation("dev.fritz2:components:_")
- }
- languageSettings.apply {
- useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi")
- }
- }
- named("jvmTest") {
- dependencies {
- implementation("io.kotest:kotest-runner-junit5:_")
- }
- }
- }
-}
-
-afterEvaluate {
- tasks {
- named("jsProcessResources", Copy::class) {
- eachFile {
- if (name == "index.html") {
- expand(project.properties + mapOf("jsOutputFileName" to jsOutputFile))
- }
- }
- }
- val jsBrowserDistribution by getting
- val compileKotlinJvm by getting
- val jvmProcessResources by getting
- create("jvmRun") {
- group = "run"
- mainClass.set(mainClassName)
- dependsOn(compileKotlinJvm, jvmProcessResources)
- classpath = files(
- configurations["jvmRuntimeClasspath"],
- compileKotlinJvm.outputs,
- jvmProcessResources.outputs,
- buildDir.resolve("dist")
- )
- }
- named("jvmJar", Jar::class) {
- dependsOn(jsBrowserDistribution)
- duplicatesStrategy = DuplicatesStrategy.WARN
-
- into("WEB-INF") {
- from(jsBrowserDistribution)
- }
- val classpath = configurations.getByName("jvmRuntimeClasspath")
- .map { if (it.isDirectory) it else zipTree(it) }
- from(classpath) {
- exclude("META-INF/*.SF")
- exclude("META-INF/*.DSA")
- exclude("META-INF/*.RSA")
- }
-
- manifest {
- attributes(
- "Main-Class" to mainClassName,
- "Built-By" to System.getProperty("user.name"),
- "Build-Jdk" to System.getProperty("java.version"),
- "Implementation-Version" to project.version,
- "Created-By" to "Gradle v${org.gradle.util.GradleVersion.current()}",
- "Created-From" to Git.headCommitHash
- )
- }
-
- inputs.property("mainClassName", mainClassName)
- inputs.files(classpath)
- inputs.files(jsBrowserDistribution.outputs)
- }
- }
-}
diff --git a/app/graalvm-musl.dockerfile b/app/graalvm-musl.dockerfile
index 406bc8d..588540e 100644
--- a/app/graalvm-musl.dockerfile
+++ b/app/graalvm-musl.dockerfile
@@ -1,4 +1,4 @@
-# registry.gitlab.com/martynas.petuska/kamp/graalvm-ce-musl:20.3.0-java11
+# registry.gitlab.com/martynas.petuska/kodex/graalvm-ce-musl:20.3.0-java11
ARG graalvmVersion=20.3.0
ARG muslVersion=1.2.1
ARG zlibVersion=1.2.11
diff --git a/app/src/commonMain/kotlin/app/domain/LibraryCount.kt b/app/src/commonMain/kotlin/app/domain/LibraryCount.kt
deleted file mode 100644
index 8e04357..0000000
--- a/app/src/commonMain/kotlin/app/domain/LibraryCount.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package app.domain
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class LibraryCount(val count: Long)
diff --git a/app/src/commonMain/kotlin/app/service/LibraryService.kt b/app/src/commonMain/kotlin/app/service/LibraryService.kt
deleted file mode 100644
index 3459ea6..0000000
--- a/app/src/commonMain/kotlin/app/service/LibraryService.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package app.service
-
-import app.domain.LibraryCount
-import app.domain.PagedResponse
-import kamp.domain.KotlinMPPLibrary
-
-expect class LibraryService {
- suspend fun getAll(
- page: Int,
- size: Int = 20,
- search: String? = null,
- targets: Set? = null,
- ): PagedResponse
-
- suspend fun getCount(search: String? = null, targets: Set? = null): LibraryCount
-
- companion object
-}
-
-val LibraryService.Companion.path get() = "/api/libraries"
diff --git a/app/src/commonMain/kotlin/app/util/DIModule.kt b/app/src/commonMain/kotlin/app/util/DIModule.kt
deleted file mode 100644
index 5f4150e..0000000
--- a/app/src/commonMain/kotlin/app/util/DIModule.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package app.util
-
-import org.kodein.di.DI
-import kotlin.properties.ReadOnlyProperty
-import kotlin.reflect.KProperty
-
-class DIModule(
- private val allowSilentOverride: Boolean = false,
- private val prefix: String = "",
- private val init: DI.Builder.() -> Unit,
-) : ReadOnlyProperty {
- private var module: DI.Module? = null
- override fun getValue(thisRef: Any?, property: KProperty<*>): DI.Module {
- return module ?: DI.Module(property.name, allowSilentOverride, prefix, init).also { module = it }
- }
-}
diff --git a/app/src/jsMain/kotlin/app/config/di.kt b/app/src/jsMain/kotlin/app/config/di.kt
deleted file mode 100644
index 52953be..0000000
--- a/app/src/jsMain/kotlin/app/config/di.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package app.config
-
-import app.service.LibraryService
-import app.util.DIModule
-import io.ktor.client.HttpClient
-import io.ktor.client.features.defaultRequest
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
-import io.ktor.http.ContentType
-import io.ktor.http.contentType
-import kotlinx.serialization.json.Json
-import org.kodein.di.DI
-import org.kodein.di.bind
-import org.kodein.di.instance
-import org.kodein.di.provider
-import org.kodein.di.singleton
-
-private val services by DIModule {
- bind() with provider { LibraryService(instance()) }
-}
-
-val di = DI {
- bind {
- provider {
- Json {}
- }
- }
- bind() with singleton {
- HttpClient {
- install(JsonFeature) {
- serializer = KotlinxSerializer(instance())
- }
- defaultRequest {
- contentType(ContentType.Application.Json)
- }
- }
- }
- import(services)
-}
diff --git a/app/src/jsMain/kotlin/app/config/env.kt b/app/src/jsMain/kotlin/app/config/env.kt
deleted file mode 100644
index 7e4033a..0000000
--- a/app/src/jsMain/kotlin/app/config/env.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package app.config
-
-import kotlinx.browser.window
-import kotlinx.coroutines.await
-import org.w3c.dom.Window
-
-external interface AppEnv {
- val API_URL: String
-}
-
-inline val Window.env: AppEnv
- get() = asDynamic().env.unsafeCast()
-
-suspend fun loadEnv(): AppEnv {
- val env: AppEnv = window.fetch("/application.env").await().text().await()
- .split("\n")
- .filter(String::isNotBlank)
- .joinToString(",", "{", "}") {
- val (key, value) = it.split("=", limit = 2).let { c ->
- c[0] to c[1]
- }
- "\"$key\": \"$value\""
- }.let(JSON::parse)
- window.asDynamic().env = env
- requireNotNull(window.env.API_URL)
- return env
-}
diff --git a/app/src/jsMain/kotlin/app/index.kt b/app/src/jsMain/kotlin/app/index.kt
deleted file mode 100644
index 1b57515..0000000
--- a/app/src/jsMain/kotlin/app/index.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package app
-
-import app.config.loadEnv
-import app.store.thunk.fetchLibraryCount
-import app.store.thunk.fetchLibraryPage
-import app.view.App
-import dev.fritz2.dom.html.render
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-
-suspend fun main() = coroutineScope {
- loadEnv()
- launch {
- fetchLibraryPage(1)(Unit)
- fetchLibraryCount()(Unit)
- }
- render("#root") {
- App()
- }
-}
diff --git a/app/src/jsMain/kotlin/app/service/LibraryService.kt b/app/src/jsMain/kotlin/app/service/LibraryService.kt
deleted file mode 100644
index dc4d5ac..0000000
--- a/app/src/jsMain/kotlin/app/service/LibraryService.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package app.service
-
-import app.domain.LibraryCount
-import app.domain.PagedResponse
-import app.util.toApi
-import io.ktor.client.HttpClient
-import io.ktor.client.request.get
-import kamp.domain.KotlinMPPLibrary
-
-actual class LibraryService(private val client: HttpClient) {
- actual suspend fun getAll(
- page: Int,
- size: Int,
- search: String?,
- targets: Set?,
- ): PagedResponse {
- val pagination = "page=$page&size=$size"
- val searchQuery = search?.let { "search=$it" } ?: ""
- val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=") ?: ""
-
- return client.get("${path}${buildQuery(pagination, searchQuery, targetsQuery)}".toApi())
- }
-
- actual suspend fun getCount(search: String?, targets: Set?): LibraryCount {
- val searchQuery = search?.let { "search=$it" }
- val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=")
-
- return client.get("$path/count${buildQuery(searchQuery, targetsQuery)}".toApi())
- }
-
- private fun buildQuery(vararg query: String?): String {
- return query.toSet().filterNotNull().takeIf(List::isNotEmpty)?.joinToString("&", prefix = "?") ?: ""
- }
-
- actual companion object
-}
diff --git a/app/src/jsMain/kotlin/app/store/LibraryStore.kt b/app/src/jsMain/kotlin/app/store/LibraryStore.kt
deleted file mode 100644
index 9857aa1..0000000
--- a/app/src/jsMain/kotlin/app/store/LibraryStore.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package app.store
-
-import app.domain.PagedResponse
-import dev.fritz2.binding.RootStore
-import kamp.domain.KotlinMPPLibrary
-
-object LibraryStore : RootStore(LibraryState()) {
- data class LibraryState(
- val libraries: PagedResponse? = null,
- val search: String? = null,
- val targets: Set? = null,
- val count: Long? = null,
- )
-
- val setLibraries =
- handleAndEmit, PagedResponse> { state, libraries ->
- emit(libraries)
- state.copy(libraries = libraries)
- }
-
- val setSearch = handleAndEmit { state, search ->
- emit(search)
- state.copy(search = search)
- }
-
- val setCount = handleAndEmit { state, count ->
- emit(count)
- state.copy(count = count)
- }
-}
diff --git a/app/src/jsMain/kotlin/app/store/thunk/appThunk.kt b/app/src/jsMain/kotlin/app/store/thunk/appThunk.kt
deleted file mode 100644
index 377d10b..0000000
--- a/app/src/jsMain/kotlin/app/store/thunk/appThunk.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package app.store.thunk
-
-import app.config.di
-import app.service.LibraryService
-import app.store.LibraryStore
-import kotlinx.browser.window
-import org.kodein.di.instance
-
-fun fetchLibraryPage(page: Int, size: Int = 12, search: String? = null, targets: Set? = null) =
- LibraryStore.handle { state ->
- val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty)
- val theTargets = (targets ?: state.targets)?.takeIf(Set::isNotEmpty)
-
- val service by di.instance()
- val libraries = service.getAll(page, size, theSearch, theTargets)
- window.scrollTo(0.0, 0.0)
- state.copy(
- libraries = libraries,
- search = theSearch,
- targets = theTargets,
- )
- }
-
-fun fetchLibraryCount(search: String? = null, targets: Set? = null) = LibraryStore.handle { state ->
- val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty)
- val theTargets = (targets ?: state.targets)?.takeIf(Set::isNotEmpty)
-
- val service by di.instance()
- val count = service.getCount(theSearch, theTargets).count
- state.copy(
- count = count,
- search = theSearch,
- targets = theTargets,
- )
-}
diff --git a/app/src/jsMain/kotlin/app/util/general.kt b/app/src/jsMain/kotlin/app/util/general.kt
deleted file mode 100644
index 28eeea3..0000000
--- a/app/src/jsMain/kotlin/app/util/general.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package app.util
-
-import app.config.env
-import app.view.KampComponent
-import dev.fritz2.dom.html.RenderContext
-import dev.fritz2.styling.params.BasicComponent
-import dev.fritz2.styling.params.BoxParams
-import dev.fritz2.styling.params.styled
-import kotlinx.browser.window
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-
-external fun require(module: String): dynamic
-
-inline fun suspending(crossinline block: suspend CoroutineScope.(T1, T2) -> Unit): (T1, T2) -> Unit =
- { t1, t2 -> suspending { block(t1, t2) } }
-
-inline fun suspending(crossinline block: suspend CoroutineScope.(T1) -> Unit): (T1) -> Unit =
- { suspending { block(it) } }
-
-inline fun suspending(crossinline block: suspend CoroutineScope.() -> Unit) {
- GlobalScope.launch { block() }
-}
-
-fun String.toApi() = "${window.env.API_URL}/${this.removePrefix("/")}"
-
-typealias StyledComponent = RenderContext.(style: BoxParams.() -> Unit, block: E.() -> Unit) -> E
-
-@KampComponent
-fun styled(component: BasicComponent): StyledComponent = { style, block ->
- (component.styled(styling = style))(block)
-}
diff --git a/app/src/jsMain/kotlin/app/view/App.kt b/app/src/jsMain/kotlin/app/view/App.kt
deleted file mode 100644
index 5a072e6..0000000
--- a/app/src/jsMain/kotlin/app/view/App.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package app.view
-
-import dev.fritz2.components.stackUp
-import dev.fritz2.dom.html.RenderContext
-import dev.fritz2.styling.staticStyle
-
-@DslMarker
-annotation class KampComponent
-
-@KampComponent
-fun RenderContext.App() {
- staticStyle(
- """
- /* width */
- ::-webkit-scrollbar {
- width: 0.75rem;
- }
-
- /* Track */
- ::-webkit-scrollbar-track {
- box-shadow: inset 0 0 5px grey;
- border-radius: 0.75rem;
- }
-
- /* Handle */
- ::-webkit-scrollbar-thumb {
- background: lightgray;
- box-shadow: inset 0 0 5px grey;
- border-radius: 0.75rem;
- }
-
- /* Handle on hover */
- ::-webkit-scrollbar-thumb:hover {
- background: gray;
- }
- """.trimIndent()
- )
- stackUp({
- height { "100%" }
- width { "100%" }
- position { relative {} }
- }) {
- Header()
- Content()
- }
-}
diff --git a/app/src/jsMain/kotlin/app/view/Content.kt b/app/src/jsMain/kotlin/app/view/Content.kt
deleted file mode 100644
index f9bceed..0000000
--- a/app/src/jsMain/kotlin/app/view/Content.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package app.view
-
-import app.store.LibraryStore
-import app.util.styled
-import app.view.component.LibraryCard
-import dev.fritz2.components.gridBox
-import dev.fritz2.components.spinner
-import dev.fritz2.components.stackUp
-import dev.fritz2.dom.html.RenderContext
-import kotlinx.coroutines.flow.map
-
-@KampComponent
-fun RenderContext.Content() {
- stackUp({
- alignItems { stretch }
- color { gray900 }
- minHeight { "100%" }
- paddings(
- sm = {
- left { small }
- right { small }
- },
- md = {
- left { larger }
- right { larger }
- },
- )
- margins {
- vertical { "5rem" }
- }
- }) {
- items {
- styled(::h2)({
- textAlign { center }
- }) { +"Kotlin Libraries" }
- gridBox({
- columns(sm = { "1fr" }, md = { repeat(2) { "1fr" } }, xl = { repeat(3) { "1fr" } })
- gap { small }
- width { "max-content" }
- css("align-self: center")
- }) {
- LibraryStore.data.map { it.libraries?.data }.render { libraries ->
- if (libraries == null) {
- spinner({
- size { large }
- }) {
- speed("1s")
- }
- } else {
- for (library in libraries) {
- LibraryCard(library)
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/src/jsMain/kotlin/app/view/Header.kt b/app/src/jsMain/kotlin/app/view/Header.kt
deleted file mode 100644
index 52604bd..0000000
--- a/app/src/jsMain/kotlin/app/view/Header.kt
+++ /dev/null
@@ -1,381 +0,0 @@
-package app.view
-
-import app.store.LibraryStore
-import app.store.thunk.fetchLibraryCount
-import app.store.thunk.fetchLibraryPage
-import app.util.styled
-import app.view.component.Badge
-import app.view.component.KampIcon
-import app.view.component.Link
-import dev.fritz2.binding.storeOf
-import dev.fritz2.components.box
-import dev.fritz2.components.checkbox
-import dev.fritz2.components.clickButton
-import dev.fritz2.components.gridBox
-import dev.fritz2.components.inputField
-import dev.fritz2.components.lineUp
-import dev.fritz2.components.modal
-import dev.fritz2.components.navBar
-import dev.fritz2.components.spinner
-import dev.fritz2.components.stackUp
-import dev.fritz2.dom.html.RenderContext
-import dev.fritz2.dom.states
-import dev.fritz2.styling.params.BasicParams
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.onEach
-
-@KampComponent
-private fun RenderContext.stackUpClose(style: BasicParams.() -> Unit = {}, children: RenderContext.() -> Unit) =
- stackUp(style) {
- spacing { none }
- items(children)
- }
-
-@KampComponent
-private fun RenderContext.SearchModal() = modal({
- maxHeight { "92.5vh" }
- overflow { auto }
-}) { close ->
- val searchStore = storeOf("")
- val targetsStore = storeOf(setOf())
-
- @KampComponent
- fun RenderContext.TargetCheckbox(values: Set, label: RenderContext.() -> Unit = {}) {
- checkbox {
- checked(targetsStore.data.map { it.containsAll(values) })
- events {
- changes.states() handledBy targetsStore.handle { targets, checked ->
- if (checked) {
- targets + values
- } else {
- targets - values
- }
- }
- }
- label(label)
- }
- }
-
- @KampComponent
- fun RenderContext.TargetCheckbox(values: Set, label: String) = TargetCheckbox(values) {
- label {
- sub { +label }
- }
- }
-
- @KampComponent
- fun RenderContext.TargetCheckboxGroup(
- targets: kotlin.collections.Map,
- header: RenderContext.() -> Unit,
- ) = stackUpClose {
- lineUp {
- spacing { none }
- items {
- TargetCheckbox(targets.values.toSet(), header)
- }
- }
- targets.forEach { (name, value) ->
- TargetCheckbox(setOf(value), name)
- }
- }
-
- size { large }
- content {
- gridBox({
- margin { auto }
- columns { "1fr" }
- maxWidth { "100%" }
- overflow { auto }
- gap { small }
- width { minContent }
- css("align-self: center")
- }) {
- box {
- h3 { +"Text Search" }
- inputField(store = searchStore) {
- type("search")
- placeholder("Search...")
- }
- }
- stackUpClose {
- h3 { +"Targets" }
- gridBox({
- columns(
- sm = { repeat(3) { "1fr" } },
- )
- gap { small }
- margins {
- bottom { small }
- }
- }) {
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "common" to "common",
- )
- ) {
- h4 { +"Metadata" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "jvm" to "jvm",
- "android" to "android",
- )
- ) {
- h4 { +"JVM" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "legacy" to "legacy",
- "ir" to "ir",
- )
- ) {
- h4 { +"JS" }
- }
- }
- }
- h4 { +"Native" }
- gridBox({
- columns(
- sm = { repeat(2) { "1fr" } },
- md = { repeat(3) { "1fr" } },
- )
- gap { small }
- }) {
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "linuxArm32Hfp" to "linux_arm32_hfp",
- "linuxArm64" to "linux_arm64",
- "linuxMips32" to "linux_mips32",
- "linuxMipsel32" to "linux_mipsel32",
- "linuxX64" to "linux_x64",
- )
- ) {
- h6 { +"Linux" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "mingwX64" to "mingw_x64",
- "mingwX86" to "mingw_x86",
- )
- ) {
- h6 { +"Windows" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "androidNativeX64" to "android_x64",
- "androidNativeX86" to "android_x86",
- "androidNativeArm32" to "android_arm32",
- "androidNativeArm64" to "android_arm64",
- )
- ) {
- h6 { +"Android NDK" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "tvosArm64" to "tvos_arm64",
- "tvosX64" to "tvos_x64",
- )
- ) {
- h6 { +"tvOS" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "iosArm32" to "ios_arm32",
- "iosArm64" to "ios_arm64",
- "iosX64" to "ios_x64",
- )
- ) {
- h6 { +"iOS" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "watchosArm32" to "watchos_arm32",
- "watchosArm64" to "watchos_arm64",
- "watchosX86" to "watchos_x86",
- "watchosX64" to "watchos_x64",
- )
- ) {
- h6 { +"watchOS" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "macosX64" to "macos_x64",
- )
- ) {
- h6 { +"macOS" }
- }
- }
- stackUpClose {
- TargetCheckboxGroup(
- mapOf(
- "wasm32" to "wasm32",
- )
- ) {
- h6 { +"WebAssembly" }
- }
- }
- }
- }
- clickButton({
- css("justify-self: center")
- width { "100%" }
- }) {
- text("Search")
- icon { fromTheme { search } }
- }.map {}.onEach {
- fetchLibraryPage(
- page = 1,
- search = searchStore.current,
- targets = targetsStore.current
- )()
- fetchLibraryCount(
- search = searchStore.current,
- targets = targetsStore.current
- )()
- } handledBy close
- }
- }
-}
-
-@KampComponent
-fun RenderContext.Pagination() = lineUp {
- spacing { none }
- items {
- LibraryStore.data.mapLatest { it.libraries }.mapNotNull { it }.render { libs ->
- clickButton({
- css("border-top-right-radius: 0")
- css("border-bottom-right-radius: 0")
- }) {
- size { small }
- icon { fromTheme { caretLeft } }
- disabled(libs.prev == null)
- } handledBy fetchLibraryPage(libs.page - 1)
- clickButton({
- css("border-radius: 0")
- }) {
- size { small }
- variant { outline }
- text("${libs.page}")
- }
- clickButton({
- css("border-top-left-radius: 0")
- css("border-bottom-left-radius: 0")
- }) {
- size { small }
- icon { fromTheme { caretRight } }
- disabled(libs.next == null)
- } handledBy fetchLibraryPage(libs.page + 1)
- }
- }
-}
-
-@KampComponent
-fun RenderContext.Header() {
- styled(::div)({
- }) {
- navBar({
- border { width { "0" } }
- boxShadow { flat }
- }) {
- brand {
- styled(::a)({
- after {
- textAlign { center }
- background { color { primary.base } }
- color { gray100 }
- }
- alignItems { end }
- }) {
- href("/")
- KampIcon {
- size { "3rem" }
- color { primary.base }
- display { inline }
- css("border-radius: 50%")
- }
-
- styled(::span)({
- margins { left { smaller } }
- verticalAlign { sub }
- fontSize(sm = { large }, md = { larger })
- fontWeight { lighter }
- display(sm = { none }, md = { initial })
- }) { +"KAMP" }
- }
- LibraryStore.data.map { it.count }.render { count ->
- Badge(
- { success },
- {
- margins {
- left { tiny }
- }
- }
- ) {
- if (count != null) {
- +"$count Lib${if (count > 1) "s" else ""}"
- } else {
- spinner {
- speed("1s")
- }
- }
- }
- }
- }
-
- actions {
- lineUp({
- display(sm = { none }, md = { flex })
- }) {
- items {
- Link("https://github.com/mpetuska/kamp", "_new") {
- +"GitHub"
- }
- }
- }
- lineUp({
- alignItems { center }
- margins {
- left { tiny }
- }
- }) {
- spacing { tiny }
- items {
- Pagination()
- val modal = SearchModal()
- clickButton({
- display(sm = { inlineBlock }, md = { none })
- }) {
- icon { fromTheme { search } }
- } handledBy modal
- clickButton({
- display(sm = { none }, md = { inlineBlock })
- }) {
- text("Search")
- icon { fromTheme { search } }
- } handledBy modal
- }
- }
- }
- }
- }
-}
diff --git a/app/src/jsMain/kotlin/app/view/component/Badge.kt b/app/src/jsMain/kotlin/app/view/component/Badge.kt
deleted file mode 100644
index c3943de..0000000
--- a/app/src/jsMain/kotlin/app/view/component/Badge.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package app.view.component
-
-import app.util.styled
-import app.view.KampComponent
-import dev.fritz2.dom.html.RenderContext
-import dev.fritz2.dom.html.Span
-import dev.fritz2.styling.params.BoxParams
-import dev.fritz2.styling.theme.Colors
-import dev.fritz2.styling.theme.Property
-
-@KampComponent
-fun RenderContext.Badge(
- color: (Colors.() -> Property) = { primary.base },
- style: BoxParams.() -> Unit = {},
- content: Span.() -> Unit = {},
-) = styled(::span)(
- {
- css("border-radius: 0.75rem")
- css("background: none repeat scroll 0% 0%")
- boxShadow { flat }
- display { inlineFlex }
- fontWeight { "500" }
- minHeight { large }
- minWidth { "1.5rem" }
- alignItems { center }
- justifyContent { spaceAround }
- textAlign { center }
- paddings {
- horizontal { small }
- vertical { tiny }
- }
- textShadow { flat }
- fontSize(
- sm = { smaller },
- md = { small }
- )
- background {
- color(color)
- }
- color { neutral }
- style()
- },
- content
-)
diff --git a/app/src/jsMain/kotlin/app/view/component/GitHubIcon.kt b/app/src/jsMain/kotlin/app/view/component/GitHubIcon.kt
deleted file mode 100644
index bf04a1d..0000000
--- a/app/src/jsMain/kotlin/app/view/component/GitHubIcon.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package app.view.component
-
-import app.view.KampComponent
-import dev.fritz2.components.icon
-import dev.fritz2.dom.html.RenderContext
-import dev.fritz2.styling.params.BasicParams
-import dev.fritz2.styling.theme.IconDefinition
-
-private val gitHubSvg = IconDefinition(
- displayName = "github",
- viewBox = "0 0 496 512",
- svg = """
-
- """.trimIndent()
-)
-
-@KampComponent
-fun RenderContext.GitHubIcon(style: BasicParams.() -> Unit = {}) {
- icon(style) { def.value = gitHubSvg }
-}
diff --git a/app/src/jsMain/kotlin/app/view/component/KampIcon.kt b/app/src/jsMain/kotlin/app/view/component/KampIcon.kt
deleted file mode 100644
index 75cd491..0000000
--- a/app/src/jsMain/kotlin/app/view/component/KampIcon.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package app.view.component
-
-import app.util.styled
-import app.view.KampComponent
-import dev.fritz2.dom.html.RenderContext
-import dev.fritz2.styling.params.BoxParams
-
-@KampComponent
-fun RenderContext.KampIcon(style: BoxParams.() -> Unit = {}) {
- styled(::img)(style) {
- src("/kamp.svg")
- }
-}
diff --git a/app/src/jsMain/kotlin/app/view/component/LibraryCard.kt b/app/src/jsMain/kotlin/app/view/component/LibraryCard.kt
deleted file mode 100644
index c936cc5..0000000
--- a/app/src/jsMain/kotlin/app/view/component/LibraryCard.kt
+++ /dev/null
@@ -1,343 +0,0 @@
-package app.view.component
-
-import app.util.styled
-import app.view.KampComponent
-import dev.fritz2.binding.RootStore
-import dev.fritz2.binding.storeOf
-import dev.fritz2.components.box
-import dev.fritz2.components.clickButton
-import dev.fritz2.components.flexBox
-import dev.fritz2.components.icon
-import dev.fritz2.components.lineUp
-import dev.fritz2.components.popover
-import dev.fritz2.components.stackUp
-import dev.fritz2.dom.html.RenderContext
-import dev.fritz2.dom.html.Span
-import dev.fritz2.styling.params.FlexParams
-import dev.fritz2.styling.params.Style
-import dev.fritz2.styling.theme.Colors
-import dev.fritz2.styling.theme.Property
-import io.ktor.http.toHttpDate
-import io.ktor.util.date.GMTDate
-import kamp.domain.KotlinMPPLibrary
-import kamp.domain.KotlinTarget
-import kotlinx.coroutines.flow.map
-
-private val String.badgeColor: Colors.() -> Property
- get() = when (this) {
- KotlinTarget.Common.category -> ({ "#47d7ff" })
- KotlinTarget.JVM.category -> ({ "#79bf2d" })
- KotlinTarget.JS.category -> ({ "#ffb100" })
- KotlinTarget.Native.category -> ({ "#6d6dff" })
- else -> ({ gray500 })
- }
-
-private fun targetPriority(target: String) = when (target) {
- KotlinTarget.Common.category -> 1
- KotlinTarget.JVM.category -> 2
- KotlinTarget.JS.category -> 3
- KotlinTarget.Native.category -> 4
- else -> 0
-}
-
-@KampComponent
-private fun RenderContext.TargetBadge(category: String, targets: List) {
- @KampComponent
- fun RenderContext.RenderBadge(content: Span.() -> Unit) {
- Badge(
- category.badgeColor,
- {
- css("cursor: pointer")
- height { large }
- margins {
- left { tiny }
- top { tiny }
- }
- },
- content
- )
- }
-
- if (targets.size > 1) {
- popover({
- width { minContent }
- css("border-radius: 0.5rem")
- paddings {
- vertical { tiny }
- }
- minWidth { "5rem" }
- textAlign { center }
- background {
- color(category.badgeColor)
- }
- }) {
- placement { bottom }
- hasCloseButton(false)
-
- toggle {
- RenderBadge {
- +category
- icon { fromTheme { caretDown } }
- }
- }
- content {
- for (target in targets) {
- styled(::div)({
- fontWeight { "500" }
- color { neutral }
- textShadow { flat }
- }) {
- +target.platform
- }
- }
- }
- }
- } else {
- RenderBadge { +(targets.firstOrNull()?.platform ?: category) }
- }
-}
-
-@KampComponent
-private fun RenderContext.CardHeader(library: KotlinMPPLibrary) {
- val groupedTargets = library.targets.groupBy(KotlinTarget::category).entries.sortedWith { (keyA), (keyB) ->
- targetPriority(keyA) - targetPriority(keyB)
- }
-
- box {
- box({
- display { flex }
- alignContent { center }
- justifyContent { spaceBetween }
- }) {
- flexBox({
- direction { column }
- }) {
- styled(::h4)({
- margins {
- top { tiny }
- }
- }) { +library.name }
- styled(::sub)({
- margin { "0" }
- }) { +library.group }
- }
- box({
- margins {
- left { small }
- }
- justifyContent { flexEnd }
- css("flex-wrap: wrap")
- display { inlineFlex }
- }) {
- for ((category, targets) in groupedTargets) {
- TargetBadge(category, targets)
- }
- }
- }
- lineUp({
- justifyContent { spaceBetween }
- }) {
- items {
- box({
- css("align-self: flex-end")
- }) {
- library.lastUpdated?.let {
- sub {
- +GMTDate(it).toHttpDate()
- }
- }
- }
- lineUp({
- justifyContent { flexEnd }
- }) {
- spacing { none }
- items {
- library.website?.let {
- Link(it, "_new") {
- +"Website"
- }
- }
- library.scm?.let {
- Link(it, "_new") {
- +"SCM"
- }
- }
- }
- }
- }
- }
- }
-}
-
-@KampComponent
-private fun RenderContext.CardBody(library: KotlinMPPLibrary, selectedVersion: RootStore) = lineUp({
- margins {
- bottom { auto }
- }
-}) {
- val boxStyle: Style = {
- paddings { horizontal { tiny } }
- overflow { auto }
- }
- items {
- box({
- maxWidth { "7rem" }
- }) {
- styled(::h5)({ }) {
- +"Versions"
- }
- box({
- css("direction: rtl")
- maxHeight { "5rem" }
- minHeight { "1rem" }
- width { "100%" }
- minWidth { "3rem" }
- boxStyle()
- }) {
- flexBox({
- css("direction: ltr")
- direction { columnReverse }
- }) {
- val versions = library.versions ?: listOf(library.version)
- for (version in versions) {
- selectedVersion.data.map { it == version }.render { isSelected ->
- clickButton({
- width { "100%" }
- height { minContent }
- paddings { vertical { "0.15rem" } }
- children("span") {
- css("text-overflow: ellipsis")
- overflow { hidden }
- }
- }) {
- size { small }
- text(version)
- variant { if (isSelected) solid else outline }
- }.map { version } handledBy selectedVersion.update
- }
- }
- }
- }
- }
- box({
- paddings { vertical { tiny } }
- maxHeight { "10rem" }
- boxStyle()
- }) {
- library.description?.let { +it }
- }
- }
-}
-
-private enum class PackageManager {
- GRADLE, MAVEN
-}
-
-@KampComponent
-private fun RenderContext.CardFooter(library: KotlinMPPLibrary, selectedVersion: RootStore) = stackUp({
- margins { top { small } }
-}) {
- val selectedPackageManager = storeOf(PackageManager.GRADLE)
-
- spacing { none }
- items {
- lineUp {
- spacing { none }
- items {
- selectedPackageManager.data.render { packageManager ->
- for (manager in PackageManager.values()) {
- box {
- clickButton({
- css("border-bottom-right-radius: 0")
- css("border-bottom-left-radius: 0")
- css("border-bottom: none")
- }) {
- variant { if (manager == packageManager) solid else outline }
- text(manager.name)
- }.map { manager } handledBy selectedPackageManager.update
- }
- }
- }
- }
- }
- box({
- width { "100%" }
- border {
- color { primary.base }
- width { thin }
- }
- css("border-bottom-left-radius: 0.5rem")
- css("border-bottom-right-radius: 0.5rem")
- css("border-top-right-radius: 0.5rem")
- }) {
- for (manager in PackageManager.values()) {
- when (manager) {
- PackageManager.GRADLE -> styled(::pre)({
- overflowX { auto }
- width { inherit }
- padding { tiny }
- }) {
- attr("hidden", selectedPackageManager.data.map { it != manager })
- selectedVersion.data.render { version ->
- domNode.innerText = "implementation(\"${library.group}:${library.name}:${version}\")"
- }
- }
- PackageManager.MAVEN -> styled(::pre)({
- overflowX { auto }
- width { inherit }
- padding { tiny }
- }) {
- attr("hidden", selectedPackageManager.data.map { it != manager })
- selectedVersion.data.render { version ->
- domNode.innerText = """|
- | ${library.group}
- | ${library.name}
- | $version
- |
- """.trimMargin()
- }
- }
- }
- }
- }
- }
-}
-
-@KampComponent
-fun RenderContext.LibraryCard(library: KotlinMPPLibrary) {
- val selectedVersionStore = storeOf(library.version)
-
- box({
- border {
- width { "1px" }
- }
- boxShadow { flat }
- css("border-radius: 0.5em")
- padding { small }
- maxWidth(
- sm = { "22.5rem" },
- lg = { "30rem" },
- )
- maxHeight(
- sm = { "22.5rem" },
- lg = { "30rem" },
- )
- display { flex }
- css("flex-direction: column")
- }) {
- CardHeader(library)
- styled(::hr)({
- borders {
- top {
- color { gray100 }
- style { solid }
- width { "0.1rem" }
- }
- }
- css("border-radius: 0.5rem")
- margins { vertical { tiny } }
- }) {}
- CardBody(library, selectedVersionStore)
- CardFooter(library, selectedVersionStore)
- }
-}
diff --git a/app/src/jsMain/kotlin/app/view/component/NavAnchor.kt b/app/src/jsMain/kotlin/app/view/component/NavAnchor.kt
deleted file mode 100644
index d3826ef..0000000
--- a/app/src/jsMain/kotlin/app/view/component/NavAnchor.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package app.view.component
-
-import app.util.styled
-import app.view.KampComponent
-import dev.fritz2.dom.html.A
-import dev.fritz2.dom.html.RenderContext
-
-@KampComponent
-fun RenderContext.Link(href: String, target: String? = null, block: A.() -> Unit = {}) {
- styled(::div)({
- radius { small }
- border {
- width { none }
- }
- hover {
- background {
- color { gray100 }
- }
- }
- paddings {
- top { tiny }
- bottom { tiny }
- left { small }
- right { small }
- }
- }) {
- styled(::a)({
- fontSize { normal }
- fontWeight { semiBold }
- color { gray900 }
- }) {
- href(href)
- target?.let { target(it) }
- block()
- }
- }
-}
diff --git a/app/src/jsMain/resources/index.html b/app/src/jsMain/resources/index.html
deleted file mode 100644
index 71d1f2c..0000000
--- a/app/src/jsMain/resources/index.html
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- KAMP
-
-
-
-
-
-
-
diff --git a/app/src/jvmMain/kotlin/app/config/di.kt b/app/src/jvmMain/kotlin/app/config/di.kt
deleted file mode 100644
index 26a1d50..0000000
--- a/app/src/jvmMain/kotlin/app/config/di.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package app.config
-
-import app.service.LibraryService
-import app.util.DIModule
-import app.util.PrivateEnv
-import io.ktor.application.Application
-import kamp.domain.KotlinMPPLibrary
-import org.kodein.di.bind
-import org.kodein.di.instance
-import org.kodein.di.ktor.CallScope
-import org.kodein.di.ktor.di
-import org.kodein.di.scoped
-import org.kodein.di.singleton
-import org.litote.kmongo.coroutine.CoroutineClient
-import org.litote.kmongo.coroutine.CoroutineCollection
-import org.litote.kmongo.coroutine.coroutine
-import org.litote.kmongo.reactivestreams.KMongo
-
-fun Application.diConfig() = di {
- import(services)
-}
-
-private val services by DIModule {
- bind() with singleton { KMongo.createClient(PrivateEnv.MONGO_STRING).coroutine }
- bind>() with singleton {
- instance().getDatabase(PrivateEnv.MONGO_DATABASE).getCollection("libraries")
- }
- bind() with scoped(CallScope).singleton { LibraryService(context, instance()) }
-}
diff --git a/app/src/jvmMain/kotlin/app/config/features.kt b/app/src/jvmMain/kotlin/app/config/features.kt
deleted file mode 100644
index 3d1ee3d..0000000
--- a/app/src/jvmMain/kotlin/app/config/features.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package app.config
-
-import app.util.PrivateEnv
-import io.ktor.application.Application
-import io.ktor.application.install
-import io.ktor.auth.Authentication
-import io.ktor.auth.UserIdPrincipal
-import io.ktor.auth.basic
-import io.ktor.features.CachingHeaders
-import io.ktor.features.CallLogging
-import io.ktor.features.Compression
-import io.ktor.features.ContentNegotiation
-import io.ktor.features.DefaultHeaders
-import io.ktor.features.StatusPages
-import io.ktor.serialization.json
-import org.slf4j.event.Level
-
-fun Application.features() {
- install(CallLogging) {
- level = Level.INFO
- }
- install(ContentNegotiation) {
- json()
- }
- install(Compression)
- install(DefaultHeaders)
- install(CachingHeaders)
- install(StatusPages)
- install(Authentication) {
- basic {
- validate { credentials ->
- if (credentials.name == PrivateEnv.ADMIN_USER && credentials.password == PrivateEnv.ADMIN_PASSWORD) {
- UserIdPrincipal(credentials.name)
- } else {
- null
- }
- }
- }
- }
-}
diff --git a/app/src/jvmMain/kotlin/app/config/routing.kt b/app/src/jvmMain/kotlin/app/config/routing.kt
deleted file mode 100644
index 2ab62b6..0000000
--- a/app/src/jvmMain/kotlin/app/config/routing.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package app.config
-
-import app.service.LibraryService
-import app.service.path
-import app.util.PublicEnv
-import app.util.inject
-import app.util.page
-import app.util.pageSize
-import app.util.search
-import app.util.targets
-import io.ktor.application.Application
-import io.ktor.application.call
-import io.ktor.auth.authenticate
-import io.ktor.http.HttpStatusCode
-import io.ktor.http.content.default
-import io.ktor.http.content.defaultResource
-import io.ktor.http.content.files
-import io.ktor.http.content.resources
-import io.ktor.http.content.static
-import io.ktor.request.receive
-import io.ktor.response.respond
-import io.ktor.response.respondText
-import io.ktor.routing.Routing
-import io.ktor.routing.get
-import io.ktor.routing.post
-import io.ktor.routing.route
-import io.ktor.routing.routing
-
-fun Application.routing() = routing {
- libraries()
- get("/application.env") { call.respondText("$PublicEnv") }
- get("/api/status") { call.respond(HttpStatusCode.OK) }
- staticContent()
-}
-
-private fun Routing.libraries() =
- route(LibraryService.path) {
- get {
- val service by inject()
- call.respond(
- service.getAll(
- call.request.page,
- call.request.pageSize,
- call.request.search,
- call.request.targets
- )
- )
- }
- get("/count") {
- val service by inject()
- call.respond(service.getCount(call.request.search, call.request.targets))
- }
-
- authenticate {
- post {
- val service by inject()
- val entity = service.create(call.receive())
- call.respond(HttpStatusCode.Created, entity)
- }
- }
- }
-
-private fun Routing.staticContent() = static {
- val folder = "WEB-INF"
- val index = "$folder/index.html"
- files(folder)
- default(index)
- resources(folder)
- defaultResource(index)
-}
diff --git a/app/src/jvmMain/kotlin/app/index.kt b/app/src/jvmMain/kotlin/app/index.kt
deleted file mode 100644
index 09f20e5..0000000
--- a/app/src/jvmMain/kotlin/app/index.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package app
-
-import app.config.diConfig
-import app.config.features
-import app.config.routing
-import app.util.PublicEnv
-import io.ktor.application.Application
-import io.ktor.application.log
-import io.ktor.server.cio.EngineMain
-
-fun main(args: Array) {
- System.setProperty("jdk.tls.client.protocols", "TLSv1.2")
- EngineMain.main(args)
-}
-
-fun Application.module() {
- features()
- routing()
- diConfig()
- log.info("ENV: $PublicEnv")
- log.debug("Full Env: ${System.getenv()}")
-}
diff --git a/app/src/jvmMain/kotlin/app/service/LibraryService.kt b/app/src/jvmMain/kotlin/app/service/LibraryService.kt
deleted file mode 100644
index 6a31590..0000000
--- a/app/src/jvmMain/kotlin/app/service/LibraryService.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package app.service
-
-import app.domain.LibraryCount
-import app.domain.PagedResponse
-import app.util.buildNextUrl
-import app.util.buildPrevUrl
-import io.ktor.application.ApplicationCall
-import kamp.domain.KotlinMPPLibrary
-import org.litote.kmongo.MongoOperator.all
-import org.litote.kmongo.MongoOperator.and
-import org.litote.kmongo.MongoOperator.language
-import org.litote.kmongo.MongoOperator.meta
-import org.litote.kmongo.MongoOperator.search
-import org.litote.kmongo.MongoOperator.text
-import org.litote.kmongo.bson
-import org.litote.kmongo.coroutine.CoroutineCollection
-
-actual class LibraryService(
- private val call: ApplicationCall,
- private val collection: CoroutineCollection,
-) {
- private fun buildQuery(_search: String?, targets: Set?): Pair {
- val searchQuery = _search?.let {
- """
- {
- $text: {
- $search: '${_search.replace("'", "\\'")}',
- $language: 'en'
- }
- }
- """.trimIndent()
- }
- val targetsQuery = targets?.let {
- """
- {
- 'targets.platform': {
- $all: [${targets.joinToString(",") { "'$it'" }}]
- }
- }
- """.trimIndent()
- }
- val finalQuery = setOfNotNull(searchQuery, targetsQuery).takeIf { it.isNotEmpty() }?.let {
- """
- {
- $and: [${it.joinToString(",")}]
- }
- """.trimIndent()
- }
-
- val projection = _search?.let {
- """
- { score: { $meta: "textScore" } }
- """.trimIndent()
- }
-
- return finalQuery to projection
- }
-
- actual suspend fun getAll(
- page: Int,
- size: Int,
- search: String?,
- targets: Set?,
- ): PagedResponse {
- val (query, projection) = buildQuery(search, targets)
-
- val dbCall = query?.let { collection.find(it) } ?: collection.find()
- projection?.let {
- dbCall.projection(it.bson)
- dbCall.sort("""{ score: { $meta: "textScore" } }""".bson)
- } ?: dbCall.ascendingSort(KotlinMPPLibrary::name)
- dbCall.skip(size * (page - 1))
- .limit(size)
- val data = dbCall.toList()
- return PagedResponse(
- data = data,
- page = page,
- next = call.request.buildNextUrl(data.size),
- prev = call.request.buildPrevUrl()
- )
- }
-
- actual suspend fun getCount(search: String?, targets: Set?): LibraryCount {
- return LibraryCount(collection.countDocuments(buildQuery(search, targets).first ?: "{}"))
- }
-
- suspend fun create(library: KotlinMPPLibrary) {
- collection.save(library)
- }
-
- actual companion object
-}
diff --git a/app/src/jvmMain/kotlin/app/util/kodein.kt b/app/src/jvmMain/kotlin/app/util/kodein.kt
deleted file mode 100644
index 39bd693..0000000
--- a/app/src/jvmMain/kotlin/app/util/kodein.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package app.util
-
-import io.ktor.application.ApplicationCall
-import io.ktor.util.pipeline.PipelineContext
-import org.kodein.di.DI
-import org.kodein.di.bindings.NoArgBindingDI
-import org.kodein.di.contexted
-import org.kodein.di.instance
-import org.kodein.di.ktor.closestDI
-import org.kodein.di.on
-import org.kodein.di.provider
-
-inline fun DI.Builder.callProvider(noinline creator: NoArgBindingDI.() -> T) =
- contexted().provider(creator)
-
-inline fun PipelineContext.inject(tag: Any? = null) =
- closestDI().on(context).instance(tag)
diff --git a/app/src/jvmMain/kotlin/app/util/paging.kt b/app/src/jvmMain/kotlin/app/util/paging.kt
deleted file mode 100644
index 681accb..0000000
--- a/app/src/jvmMain/kotlin/app/util/paging.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package app.util
-
-import io.ktor.http.URLBuilder
-import io.ktor.request.ApplicationRequest
-import io.ktor.request.port
-import io.ktor.request.uri
-
-fun ApplicationRequest.buildNextUrl(currentElementCount: Int): String? = if (currentElementCount == pageSize) {
- URLBuilder(call.request.uri).apply {
- port = call.request.port()
- parameters["page"] = "${page + 1}"
- parameters["size"] = "$pageSize"
- }.buildString()
-} else {
- null
-}
-
-fun ApplicationRequest.buildPrevUrl(): String? = if (page > 1) {
- URLBuilder(call.request.uri).apply {
- port = call.request.port()
- parameters["page"] = "${page - 1}"
- parameters["size"] = "$pageSize"
- }.buildString()
-} else {
- null
-}
-
-val ApplicationRequest.page get() = queryParameters["page"]?.let(String::toIntOrNull) ?: 1
-val ApplicationRequest.pageSize get() = queryParameters["size"]?.let(String::toIntOrNull) ?: 50
-val ApplicationRequest.search get() = queryParameters["search"]?.takeIf { it.isNotBlank() }
-val ApplicationRequest.targets get() = queryParameters.getAll("target")?.toSet()?.takeIf { it.isNotEmpty() }
diff --git a/app/src/jvmMain/resources/logback.xml b/app/src/jvmMain/resources/logback.xml
deleted file mode 100644
index 502c6dd..0000000
--- a/app/src/jvmMain/resources/logback.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- [%-5.5level] %d{HH:mm:ss} [%-20.20thread] %-30.30logger{30} - %msg%n
-
-
-
-
-
-
-
-
diff --git a/build-conventions/build.gradle.kts b/build-conventions/build.gradle.kts
new file mode 100644
index 0000000..06db05f
--- /dev/null
+++ b/build-conventions/build.gradle.kts
@@ -0,0 +1,39 @@
+plugins {
+ `kotlin-dsl`
+ alias(libs.plugins.versions)
+ alias(libs.plugins.versions.update)
+}
+
+repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ google()
+}
+
+dependencies {
+ implementation(libs.plugin.android)
+ implementation(libs.plugin.git.hooks)
+ implementation(libs.plugin.detekt)
+ implementation(libs.plugin.compose)
+ implementation(libs.plugin.kotlin)
+ implementation(libs.plugin.shadow)
+
+ implementation(libs.plugin.versions)
+ implementation(libs.plugin.versions.update)
+ implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
+}
+
+versionCatalogUpdate {
+ keep {
+ keepUnusedVersions.set(true)
+ keepUnusedLibraries.set(true)
+ keepUnusedPlugins.set(true)
+ }
+}
+
+gradleEnterprise {
+ buildScan {
+ termsOfServiceUrl = "https://gradle.com/terms-of-service"
+ termsOfServiceAgree = "yes"
+ }
+}
diff --git a/build-conventions/gradle b/build-conventions/gradle
new file mode 120000
index 0000000..3337596
--- /dev/null
+++ b/build-conventions/gradle
@@ -0,0 +1 @@
+../gradle
\ No newline at end of file
diff --git a/build-conventions/gradle.properties b/build-conventions/gradle.properties
new file mode 120000
index 0000000..7677fb7
--- /dev/null
+++ b/build-conventions/gradle.properties
@@ -0,0 +1 @@
+../gradle.properties
\ No newline at end of file
diff --git a/build-conventions/settings.gradle.kts b/build-conventions/settings.gradle.kts
new file mode 100644
index 0000000..81161ac
--- /dev/null
+++ b/build-conventions/settings.gradle.kts
@@ -0,0 +1,12 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+plugins {
+ id("com.gradle.enterprise") version "3.12.3"
+}
+
+rootProject.name = "build-conventions"
diff --git a/build-conventions/src/main/kotlin/convention.app-android.gradle.kts b/build-conventions/src/main/kotlin/convention.app-android.gradle.kts
new file mode 100644
index 0000000..3a14c1c
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.app-android.gradle.kts
@@ -0,0 +1,28 @@
+import ext.AndroidAppExtension
+import ext.AppExtension
+
+plugins {
+ id("com.android.application")
+ id("convention.app-common")
+}
+
+val androidApp = the().extensions.create("android").apply {
+}
+
+android {
+ namespace = "${rootProject.group}.${rootProject.name}.${project.name}"
+ compileSdk = 33
+ defaultConfig {
+ minSdk = 26
+ targetSdk = 33
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+}
+
+kotlin {
+ android()
+}
diff --git a/build-conventions/src/main/kotlin/convention.app-common.gradle.kts b/build-conventions/src/main/kotlin/convention.app-common.gradle.kts
new file mode 100644
index 0000000..7deb5a1
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.app-common.gradle.kts
@@ -0,0 +1,9 @@
+import ext.AppExtension
+
+plugins {
+ id("convention.common")
+ kotlin("multiplatform")
+}
+
+val app = extensions.create("app").apply {
+}
diff --git a/build-conventions/src/main/kotlin/convention.app-compose.gradle.kts b/build-conventions/src/main/kotlin/convention.app-compose.gradle.kts
new file mode 100644
index 0000000..8e23c7d
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.app-compose.gradle.kts
@@ -0,0 +1,59 @@
+// import ext.AppExtension
+// import ext.JvmAppExtension
+//
+// plugins {
+// id("convention.app-mpp")
+// id("convention.compose")
+// }
+//
+// extensions.configure {
+// the().fatJar.set(false)
+// }
+//
+// kotlin {
+// sourceSets {
+// commonMain {
+// dependencies {
+// implementation(compose.runtime)
+// }
+// }
+// jsMain {
+// dependencies {
+// implementation(compose.web.core)
+// implementation(compose.web.svg)
+// }
+// }
+// jsTest {
+// dependencies {
+// implementation(compose.web.testUtils)
+// }
+// languageSettings {
+// optIn("org.jetbrains.compose.web.testutils.ComposeWebExperimentalTestsApi")
+// }
+// }
+// androidMain {
+// dependencies {
+// implementation("androidx.activity:activity-compose:_")
+// implementation("androidx.appcompat:appcompat:_")
+// implementation("androidx.core:core-ktx:_")
+// }
+// }
+// jvmMain {
+// dependencies {
+// implementation(compose.desktop.currentOs)
+// }
+// }
+// jvmCommonMain {
+// dependencies {
+// @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
+// implementation(compose.material3)
+// }
+// }
+// jvmCommonInstrumentedTest {
+// dependencies {
+// @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
+// implementation(compose.uiTestJUnit4)
+// }
+// }
+// }
+// }
diff --git a/build-conventions/src/main/kotlin/convention.app-js.gradle.kts b/build-conventions/src/main/kotlin/convention.app-js.gradle.kts
new file mode 100644
index 0000000..0673317
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.app-js.gradle.kts
@@ -0,0 +1,60 @@
+import ext.AppExtension
+import ext.JsAppExtension
+import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
+import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.DevServer
+
+plugins {
+ id("convention.app-common")
+}
+
+val app = the().extensions.create("js").apply {
+ outputFileName.convention("${project.name}-${project.version}.js")
+ distributionDir.convention(buildDir.resolve("dist/js/WEB-INF"))
+ devServer.convention(Action {})
+ devServer {
+ port = 3000
+ proxy = mutableMapOf("/api/*" to "http://localhost:8080")
+ open = false
+ }
+}
+
+kotlin {
+ js(IR) {
+ binaries.executable()
+ useCommonJs()
+ browser {
+ @OptIn(ExperimentalDistributionDsl::class)
+ distribution {
+ directory = app.distributionDir.get()
+ }
+ commonWebpackConfig {
+ outputFileName = app.outputFileName.get()
+ devServer = (devServer ?: DevServer()).apply(app.devServer.get()::execute)
+ configDirectory = rootDir.resolve("gradle/webpack.config.d")
+ cssSupport { enabled.set(true) }
+ scssSupport { enabled.set(true) }
+ }
+ runTask {
+ outputFileName = app.outputFileName.get()
+ }
+ }
+ }
+}
+
+tasks {
+ named("jsProcessResources", Copy::class) {
+ eachFile {
+ if (name == "index.html") {
+ expand(
+ project.properties + mapOf(
+ "jsOutputFileName" to app.outputFileName.get(),
+ "outputFileName" to app.outputFileName.get(),
+ )
+ )
+ }
+ }
+ }
+ matching { it.name.startsWith("js") && it.name.endsWith("Run") }.configureEach {
+ group = "run"
+ }
+}
diff --git a/build-conventions/src/main/kotlin/convention.app-jvm.gradle.kts b/build-conventions/src/main/kotlin/convention.app-jvm.gradle.kts
new file mode 100644
index 0000000..3a743eb
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.app-jvm.gradle.kts
@@ -0,0 +1,56 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import ext.AppExtension
+import ext.JvmAppExtension
+import util.Git
+
+plugins {
+ id("convention.app-common")
+ id("com.github.johnrengelman.shadow")
+}
+
+val app = the().extensions.create("jvm").apply {
+ fatJar.convention(true)
+}
+
+kotlin {
+ jvm()
+}
+
+tasks {
+ val runtimeClasspath = configurations.named("jvmRuntimeClasspath")
+ val compileKotlin = named("compileKotlinJvm")
+ val processResources = named("jvmProcessResources")
+ val fatJar = register("jvmFatJar") {
+ onlyIf { app.fatJar.get() }
+ group = "build"
+ manifest {
+ attributes(
+ mapOf(
+ "Built-By" to System.getProperty("user.name"),
+ "Build-Jdk" to System.getProperty("java.version"),
+ "Implementation-Version" to project.version,
+ "Created-By" to "${GradleVersion.current()}",
+ "Created-From" to Git.headCommitHash
+ ) + (app.mainClass.orNull?.let { mapOf("Main-Class" to it) } ?: mapOf())
+ )
+ }
+ mergeServiceFiles()
+ archiveAppendix.set("jvm")
+ archiveClassifier.set("fat")
+ from(compileKotlin, processResources)
+ configurations.add(runtimeClasspath.get())
+ inputs.property("mainClassName", app.mainClass)
+ }
+ assemble {
+ dependsOn(fatJar)
+ }
+ register("jvmRun") {
+ onlyIf { app.mainClass.isPresent }
+ classpath(compileKotlin, processResources, runtimeClasspath)
+ mainClass.set(app.mainClass)
+ inputs.property("mainClass", app.mainClass)
+ }
+ withType {
+ group = "run"
+ }
+}
diff --git a/build-conventions/src/main/kotlin/convention.common.gradle.kts b/build-conventions/src/main/kotlin/convention.common.gradle.kts
new file mode 100644
index 0000000..d991390
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.common.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ id("convention.local-properties")
+ id("convention.detekt")
+}
+
+repositories {
+ mavenCentral()
+ google()
+}
diff --git a/build-conventions/src/main/kotlin/convention.compose.gradle.kts b/build-conventions/src/main/kotlin/convention.compose.gradle.kts
new file mode 100644
index 0000000..8aceafb
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.compose.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("org.jetbrains.compose")
+}
diff --git a/build-conventions/src/main/kotlin/convention.detekt.gradle.kts b/build-conventions/src/main/kotlin/convention.detekt.gradle.kts
new file mode 100644
index 0000000..2bce782
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.detekt.gradle.kts
@@ -0,0 +1,49 @@
+import io.gitlab.arturbosch.detekt.Detekt
+import org.gradle.accessors.dm.LibrariesForLibs
+
+plugins {
+ id("io.gitlab.arturbosch.detekt")
+}
+
+val libs = the()
+
+dependencies {
+ detektPlugins(libs.detekt.formatting)
+}
+
+detekt {
+ config.from(rootDir.resolve("gradle/detekt.yml"))
+ buildUponDefaultConfig = true
+ source.from(files("src/", "*.kts"))
+}
+
+tasks {
+ if (project == rootProject) {
+ register("detektAll", Detekt::class) {
+ description = "Run Detekt for all modules"
+ config.from(project.detekt.config)
+ buildUponDefaultConfig = project.detekt.buildUponDefaultConfig
+ setSource(files(projectDir))
+ }
+ }
+ afterEvaluate {
+ withType {
+ parallel = true
+ reports {
+ // observe findings in your browser with structure and code snippets
+ html.required.set(true)
+ // checkstyle like format mainly for integrations like Jenkins
+ xml.required.set(true)
+ // similar to the console output, contains issue signature to manually edit baseline files
+ txt.required.set(true)
+ /*
+ * standardized SARIF format (https://sarifweb.azurewebsites.net/)
+ * to support integrations with Github Code Scanning
+ */
+ sarif.required.set(true)
+ }
+ include("**/*.kt", "**/*.kts")
+ exclude("**/build", "scripts/")
+ }
+ }
+}
diff --git a/build-conventions/src/main/kotlin/convention.git-hooks.gradle.kts b/build-conventions/src/main/kotlin/convention.git-hooks.gradle.kts
new file mode 100644
index 0000000..8198f8c
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.git-hooks.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("convention.detekt")
+ id("com.github.jakemarsden.git-hooks")
+}
+
+gitHooks {
+ setHooks(
+ mapOf(
+ "pre-commit" to "detektAll --auto-correct",
+ "pre-push" to "detektAll"
+ )
+ )
+}
diff --git a/build-conventions/src/main/kotlin/convention.library-android.gradle.kts b/build-conventions/src/main/kotlin/convention.library-android.gradle.kts
new file mode 100644
index 0000000..abe6043
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.library-android.gradle.kts
@@ -0,0 +1,28 @@
+plugins {
+ id("com.android.library")
+ id("convention.library-common")
+}
+
+android {
+ compileSdk = 33
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ defaultConfig {
+ minSdk = 21
+ targetSdk = 33
+ publishing {
+ multipleVariants {
+ withSourcesJar()
+ withJavadocJar()
+ allVariants()
+ }
+ }
+ }
+}
+
+kotlin {
+ android()
+ jvmToolchain(11)
+}
diff --git a/build-conventions/src/main/kotlin/convention.library-common.gradle.kts b/build-conventions/src/main/kotlin/convention.library-common.gradle.kts
new file mode 100644
index 0000000..6e11f70
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.library-common.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+ id("convention.common")
+ kotlin("multiplatform")
+}
+
+kotlin {
+ sourceSets {
+ commonTest {
+ dependencies {
+ implementation(kotlin("test"))
+ }
+ }
+ }
+}
diff --git a/build-conventions/src/main/kotlin/convention.library-js.gradle.kts b/build-conventions/src/main/kotlin/convention.library-js.gradle.kts
new file mode 100644
index 0000000..eff4277
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.library-js.gradle.kts
@@ -0,0 +1,17 @@
+plugins {
+ id("convention.library-common")
+}
+
+kotlin {
+ js(IR) {
+ useEsModules()
+ browser {
+ commonWebpackConfig {
+ cssSupport { enabled.set(true) }
+ scssSupport { enabled.set(true) }
+ configDirectory = project.rootDir.resolve("gradle/webpack.config.d")
+ }
+ testTask { useKarma {} }
+ }
+ }
+}
diff --git a/build-conventions/src/main/kotlin/convention.library-jvm.gradle.kts b/build-conventions/src/main/kotlin/convention.library-jvm.gradle.kts
new file mode 100644
index 0000000..1dcb940
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.library-jvm.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ id("convention.library-common")
+}
+
+kotlin {
+ jvm()
+ jvmToolchain(11)
+}
diff --git a/build-conventions/src/main/kotlin/convention.local-properties.gradle.kts b/build-conventions/src/main/kotlin/convention.local-properties.gradle.kts
new file mode 100644
index 0000000..86c4674
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.local-properties.gradle.kts
@@ -0,0 +1,15 @@
+import java.util.*
+
+
+fun loadLocalProperties(file: File) {
+ file.takeIf(File::exists)?.let { f ->
+ Properties().apply {
+ f.inputStream().use(::load)
+ }.mapKeys { (k, _) -> k.toString() }
+ }?.toList()?.forEach { (k, v) ->
+ project.extra[k] = v
+ }
+}
+
+loadLocalProperties(rootDir.resolve("local.properties"))
+loadLocalProperties(projectDir.resolve("local.properties"))
diff --git a/build-conventions/src/main/kotlin/convention.versions.gradle.kts b/build-conventions/src/main/kotlin/convention.versions.gradle.kts
new file mode 100644
index 0000000..f245fcf
--- /dev/null
+++ b/build-conventions/src/main/kotlin/convention.versions.gradle.kts
@@ -0,0 +1,30 @@
+import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
+import java.util.*
+
+plugins {
+ id("com.github.ben-manes.versions")
+ id("nl.littlerobots.version-catalog-update")
+}
+
+versionCatalogUpdate {
+ keep {
+ keepUnusedVersions by true
+ keepUnusedLibraries by true
+ keepUnusedPlugins by true
+ }
+}
+
+tasks {
+ withType {
+ gradleReleaseChannel = "current"
+ rejectVersionIf {
+ val isNonStable: (String) -> Boolean = { version ->
+ val stableKeyword = setOf("RELEASE", "FINAL", "GA")
+ .any { version.uppercase(Locale.ROOT).contains(it) }
+ val regex = "^[0-9,.v-]+(-r)?$".toRegex()
+ !stableKeyword && !(version matches regex)
+ }
+ isNonStable(candidate.version) && !isNonStable(currentVersion)
+ }
+ }
+}
diff --git a/build-conventions/src/main/kotlin/ext/AndroidAppExtension.kt b/build-conventions/src/main/kotlin/ext/AndroidAppExtension.kt
new file mode 100644
index 0000000..c53d50d
--- /dev/null
+++ b/build-conventions/src/main/kotlin/ext/AndroidAppExtension.kt
@@ -0,0 +1,3 @@
+package ext
+
+interface AndroidAppExtension : AppExtension
diff --git a/build-conventions/src/main/kotlin/ext/AppExtension.kt b/build-conventions/src/main/kotlin/ext/AppExtension.kt
new file mode 100644
index 0000000..34d8b63
--- /dev/null
+++ b/build-conventions/src/main/kotlin/ext/AppExtension.kt
@@ -0,0 +1,10 @@
+package ext
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.ExtensionAware
+import javax.inject.Inject
+
+interface AppExtension : ExtensionAware {
+ @get:Inject
+ val project: Project
+}
diff --git a/build-conventions/src/main/kotlin/ext/JsAppExtension.kt b/build-conventions/src/main/kotlin/ext/JsAppExtension.kt
new file mode 100644
index 0000000..f7a00db
--- /dev/null
+++ b/build-conventions/src/main/kotlin/ext/JsAppExtension.kt
@@ -0,0 +1,21 @@
+package ext
+
+import org.gradle.api.Action
+import org.gradle.api.provider.Property
+import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.DevServer
+import java.io.File
+
+interface JsAppExtension : AppExtension {
+ val outputFileName: Property
+ val distributionDir: Property
+ val devServer: Property>
+ fun devServer(action: Action) {
+ val old = devServer.get()
+ devServer.set(
+ Action {
+ old.execute(this)
+ action.execute(this)
+ }
+ )
+ }
+}
diff --git a/build-conventions/src/main/kotlin/ext/JvmAppExtension.kt b/build-conventions/src/main/kotlin/ext/JvmAppExtension.kt
new file mode 100644
index 0000000..26ebc9d
--- /dev/null
+++ b/build-conventions/src/main/kotlin/ext/JvmAppExtension.kt
@@ -0,0 +1,8 @@
+package ext
+
+import org.gradle.api.provider.Property
+
+interface JvmAppExtension : AppExtension {
+ val mainClass: Property
+ val fatJar: Property
+}
diff --git a/build-conventions/src/main/kotlin/util/_global.kt b/build-conventions/src/main/kotlin/util/_global.kt
new file mode 100644
index 0000000..2673994
--- /dev/null
+++ b/build-conventions/src/main/kotlin/util/_global.kt
@@ -0,0 +1,7 @@
+@file:Suppress("PackageDirectoryMismatch")
+
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+
+infix fun Property.by(value: T) = set(value)
+infix fun Property.by(value: Provider) = set(value)
diff --git a/build-conventions/src/main/kotlin/util/gradle.kt b/build-conventions/src/main/kotlin/util/gradle.kt
new file mode 100644
index 0000000..1643dd3
--- /dev/null
+++ b/build-conventions/src/main/kotlin/util/gradle.kt
@@ -0,0 +1,38 @@
+package util
+
+import org.gradle.api.Action
+import org.gradle.api.NamedDomainObjectContainer
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import java.nio.charset.Charset
+import kotlin.properties.ReadOnlyProperty
+
+object Git {
+ val headCommitHash by lazy { execAndCapture("git rev-parse --verify HEAD") }
+}
+
+fun execAndCapture(cmd: String): String? {
+ val child = Runtime.getRuntime().exec(cmd)
+ child.waitFor()
+ return if (child.exitValue() == 0) {
+ child.inputStream.readAllBytes().toString(Charset.defaultCharset()).trim()
+ } else {
+ null
+ }
+}
+
+fun linkedSourceSets(
+ vararg sourceSets: String
+) = ReadOnlyProperty<
+ NamedDomainObjectContainer,
+ (action: Action) -> Unit
+ > { thisRef, property ->
+ sourceSets.forEach {
+ thisRef.named(it) {
+ kotlin.srcDir("src/${property.name}/kotlin")
+ resources.srcDir("src/${property.name}/resources")
+ }
+ }
+ return@ReadOnlyProperty {
+ sourceSets.forEach { ss -> thisRef.named(ss, it) }
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 7bcba47..2af4a68 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,21 +1,12 @@
plugins {
- id("com.github.jakemarsden.git-hooks")
- idea
+ if (System.getenv("CI") == null) id("convention.git-hooks")
+ id("convention.common")
+ id("convention.versions")
}
-gitHooks {
- setHooks(
- mapOf(
- "post-checkout" to "ktlintApplyToIdea",
- "pre-commit" to "ktlintFormat",
- "pre-push" to "ktlintCheck"
- )
- )
-}
-
-idea {
- module {
- isDownloadSources = true
- isDownloadJavadoc = true
+gradleEnterprise {
+ buildScan {
+ termsOfServiceUrl = "https://gradle.com/terms-of-service"
+ termsOfServiceAgree = "yes"
}
}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
deleted file mode 100644
index bfc67f2..0000000
--- a/buildSrc/build.gradle.kts
+++ /dev/null
@@ -1,9 +0,0 @@
-plugins {
- `kotlin-dsl`
-}
-
-repositories {
- gradlePluginPortal()
- mavenCentral()
- mavenLocal()
-}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
deleted file mode 100644
index 436c27c..0000000
--- a/buildSrc/settings.gradle.kts
+++ /dev/null
@@ -1,9 +0,0 @@
-pluginManagement {
- plugins {
- id("de.fayard.refreshVersions") version "0.40.1"
- }
-}
-
-plugins {
- id("de.fayard.refreshVersions")
-}
diff --git a/buildSrc/src/main/kotlin/util.kt b/buildSrc/src/main/kotlin/util.kt
deleted file mode 100644
index dd7788b..0000000
--- a/buildSrc/src/main/kotlin/util.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-import groovy.lang.*
-import org.gradle.api.provider.*
-import java.io.*
-import java.nio.charset.*
-
-typealias Lambda = R.() -> V
-
-fun Lambda.toClosure(owner: Any? = null, thisObj: Any? = null) = object : Closure(owner, thisObj) {
- @Suppress("UNCHECKED_CAST")
- fun doCall() {
- with(delegate as R) {
- this@toClosure()
- }
- }
-}
-
-fun closureOf(owner: Any? = null, thisObj: Any? = null, func: R.() -> V) = func.toClosure(owner, thisObj)
-
-infix fun Property.by(value: T) {
- set(value)
-}
-
-object Git {
- val headCommitHash by lazy {
- val child = Runtime.getRuntime().exec("git rev-parse --verify HEAD")
- child.waitFor()
- child.inputStream.readAllBytes().toString(Charset.defaultCharset()).trim()
- }
-}
diff --git a/common/build.gradle.kts b/common/build.gradle.kts
deleted file mode 100644
index 5f6be06..0000000
--- a/common/build.gradle.kts
+++ /dev/null
@@ -1,48 +0,0 @@
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
-plugins {
- kotlin("multiplatform")
- kotlin("plugin.serialization")
- id("org.jlleitschuh.gradle.ktlint")
-}
-
-repositories {
- mavenCentral()
-}
-
-tasks {
- withType {
- useJUnitPlatform()
- }
- withType {
- kotlinOptions {
- jvmTarget = "${JavaVersion.VERSION_11}"
- }
- }
-}
-
-kotlin {
- explicitApi()
- jvm()
- js {
- browser()
- }
-
- sourceSets {
- named("commonMain") {
- dependencies {
- api("io.ktor:ktor-client-serialization:_")
- }
- }
- named("jvmMain") {
- dependencies {
- api(kotlin("reflect"))
- }
- }
- all {
- languageSettings.apply {
- useExperimentalAnnotation("kotlin.ExperimentalStdlibApi")
- }
- }
- }
-}
diff --git a/common/src/commonMain/kotlin/kamp/domain/KotlinTarget.kt b/common/src/commonMain/kotlin/kamp/domain/KotlinTarget.kt
deleted file mode 100644
index f7551c2..0000000
--- a/common/src/commonMain/kotlin/kamp/domain/KotlinTarget.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package kamp.domain
-
-import kotlinx.serialization.Serializable
-
-@Serializable
-public class KotlinTarget private constructor(
- public val category: String,
- public val platform: String,
-) {
-
- override fun toString(): String = "$category:$platform"
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is KotlinTarget) return false
-
- if (platform != other.platform) return false
- if (category != other.category) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = platform.hashCode()
- result = 31 * result + category.hashCode()
- return result
- }
-
- public object Common {
- public const val category: String = "common"
- public operator fun invoke(): KotlinTarget = KotlinTarget(category, category)
- }
-
- public object JS {
- public const val category: String = "js"
- public fun Legacy(): KotlinTarget = KotlinTarget(category, "legacy")
- public fun IR(): KotlinTarget = KotlinTarget(category, "ir")
- }
-
- public object JVM {
- public const val category: String = "jvm"
- public fun Java(): KotlinTarget = KotlinTarget(category, "jvm")
- public fun Android(): KotlinTarget = KotlinTarget(category, "android")
- }
-
- public object Native {
- public const val category: String = "native"
- public operator fun invoke(platform: String): KotlinTarget = KotlinTarget(category, platform)
- }
-}
diff --git a/common/src/commonMain/kotlin/kamp/domain/MavenArtifact.kt b/common/src/commonMain/kotlin/kamp/domain/MavenArtifact.kt
deleted file mode 100644
index 12ad2a7..0000000
--- a/common/src/commonMain/kotlin/kamp/domain/MavenArtifact.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package kamp.domain
-
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.Transient
-
-public interface MavenArtifact {
- public val group: String
- public val name: String
- public val latestVersion: String
- public val releaseVersion: String?
- public val versions: List?
- public val lastUpdated: Long?
-
- @Transient
- public val version: String
- get() = releaseVersion ?: latestVersion
-
- @Transient
- public val path: String
- get() = "$group:$name:$version"
-}
-
-@Serializable
-public data class MavenArtifactImpl(
- override val group: String,
- override val name: String,
- override val latestVersion: String,
- override val releaseVersion: String?,
- override val versions: List?,
- override val lastUpdated: Long?,
-) : MavenArtifact
diff --git a/common/src/jvmMain/kotlin/kamp/util/Env.kt b/common/src/jvmMain/kotlin/kamp/util/Env.kt
deleted file mode 100644
index 77e0ed4..0000000
--- a/common/src/jvmMain/kotlin/kamp/util/Env.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package kamp.util
-
-import kotlin.properties.ReadOnlyProperty
-import kotlin.reflect.KProperty
-import kotlin.reflect.KProperty1
-import kotlin.reflect.full.memberProperties
-
-public abstract class Env {
- protected class EnvDelegate(private val converter: EnvDelegate<*>.(String?) -> T) : ReadOnlyProperty {
- private val camelSplitRegex = "(?): T {
- val env = System.getenv()
- val value = env[property.name]
- ?: env[property.name.uppercase().replace("-", "_")]
- ?: env[property.name.split(camelSplitRegex).joinToString("_") { it.uppercase() }]
- return converter(value)
- }
-
- public fun findEnv(name: String): String? = System.getenv()[name]
- }
-
- override fun toString(): String = this::class.memberProperties.joinToString("\n") {
- @Suppress("UNCHECKED_CAST")
- "${it.name.uppercase()}=${(it as KProperty1).get(this)}"
- }
-}
diff --git a/gradle.properties b/gradle.properties
index 05296f4..4860765 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,8 +1,19 @@
+#======================================== Gradle ========================================
+org.gradle.vfs.watch=true
+org.gradle.cache=true
+org.gradle.parallel=true
+org.gradle.jvmargs=-XX:MaxMetaspaceSize=2g -Xmx2g
+#======================================== Kotlin ========================================
kotlin.style=official
-kotlin.parallel.tasks.in.project=true
-kotlin.mpp.stability.nowarn=true
+kotlin.stdlib.default.dependency=true
kotlin.js.generate.externals=false
kotlin.js.compiler=ir
-kotlin.incremental.js=true
-version=0.0.0
-group=lt.petuska
+kotlin.mpp.stability.nowarn=true
+kotlin.native.ignoreDisabledTargets=true
+kotlin.js.browser.karma.browsers=chromium-headless
+kotlin.mpp.androidSourceSetLayoutVersion=2
+#======================================= Android ========================================
+android.useAndroidX=true
+#======================================= Project ========================================
+group=dev.petuska
+version=0.0.0-SNAPSHOT
diff --git a/gradle/detekt.yml b/gradle/detekt.yml
new file mode 100644
index 0000000..b858c85
--- /dev/null
+++ b/gradle/detekt.yml
@@ -0,0 +1,66 @@
+config:
+ warningsAsErrors: true
+
+complexity:
+ LongMethod:
+ ignoreAnnotated:
+ - Showcase
+ TooManyFunctions:
+ ignoreAnnotated:
+ - MDCExternalAPI
+ LongParameterList:
+ ignoreAnnotated:
+ - MDCDsl
+ - Composable
+
+comments:
+ active: true
+ CommentOverPrivateFunction:
+ active: false
+ CommentOverPrivateProperty:
+ active: false
+ DeprecatedBlockTag:
+ active: true
+ OutdatedDocumentation:
+ active: true
+ allowParamOnConstructorProperties: false
+# UndocumentedPublicClass:
+# active: true
+# UndocumentedPublicFunction:
+# active: true
+# UndocumentedPublicProperty:
+# active: true
+
+naming:
+ InvalidPackageDeclaration:
+ active: false
+ MatchingDeclarationName:
+ active: false
+ TopLevelPropertyNaming:
+ constantPattern: '[A-Z][_a-zA-Z0-9]*'
+ FunctionNaming:
+ ignoreAnnotated:
+ - androidx.compose.runtime.Composable
+
+style:
+ ForbiddenComment:
+ active: false
+ WildcardImport:
+ active: false
+ UnnecessaryAbstractClass:
+ active: false
+ MaxLineLength:
+ active: true
+ maxLineLength: 100
+ MagicNumber:
+ active: false
+
+formatting:
+ Filename:
+ active: false
+ NoWildcardImports:
+ active: false
+ Indentation:
+ indentSize: 2
+ FinalNewline:
+ active: true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..292b295
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,103 @@
+[versions]
+build-config = "4.1.1"
+clikt = "4.0.0"
+compose = "1.4.1"
+compose-routing = "0.2.12"
+detekt = "1.23.0"
+dokka = "1.8.20"
+jsoup = "1.16.1"
+kmdc = "0.1.1"
+kmongo = "4.9.0"
+koin = "3.4.2"
+koin-android = "3.4.2"
+koin-androidx = "3.4.5"
+koin-annotations = "1.2.2"
+koin-compose = "1.0.3"
+koin-ktor = "3.4.1"
+kotest = "5.6.2"
+kotlin = "1.8.20"
+kotlinx-coroutines = "1.7.2"
+kotlinx-serialization = "1.5.1"
+ksp = "1.8.20-1.0.11"
+ktor = "2.3.2"
+logback = "1.4.8"
+plugin-android = "7.3.1"
+plugin-git-hooks = "0.0.2"
+plugin-shadow = "7.0.0"
+plugin-versions = "0.47.0"
+plugin-versions-update = "0.8.0"
+redux = "0.6.0"
+redux-compose = "0.6.0"
+redux-thunk = "0.6.0"
+
+[libraries]
+androidx-lifecycle-compose = "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"
+redux-threadsafe = { module = "org.reduxkotlin:redux-kotlin-threadsafe", version.ref = "redux" }
+redux-compose = { module = "org.reduxkotlin:redux-kotlin-compose", version.ref = "redux-compose" }
+redux-thunk = { module = "org.reduxkotlin:redux-kotlin-thunk", version.ref = "redux-thunk" }
+clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
+compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "compose" }
+compose-html-svg = { module = "org.jetbrains.compose.html:html-svg", version.ref = "compose" }
+compose-routing = { module = "app.softwork:routing-compose", version.ref = "compose-routing" }
+compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "compose" }
+detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
+jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
+kmdc = { module = "dev.petuska:kmdc", version.ref = "kmdc" }
+kmongo = { module = "org.litote.kmongo:kmongo-coroutine-serialization", version.ref = "kmongo" }
+koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-annotations" }
+koin-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koin-annotations" }
+koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-compose" }
+koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
+koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin-ktor" }
+koin-logger-slf4j = { module = "io.insert-koin:koin-logger-slf4j", version.ref = "koin-ktor" }
+kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
+kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
+kotlin-test-annotations-common = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" }
+kotlin-test-common = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "kotlin" }
+kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kotlin" }
+kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
+kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" }
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
+kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
+kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinx-serialization" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
+ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
+ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
+ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
+ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
+ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
+ktor-serialization-kotlinx-cbor = { module = "io.ktor:ktor-serialization-kotlinx-cbor", version.ref = "ktor" }
+ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
+ktor-server-auth = { module = "io.ktor:ktor-server-auth", version.ref = "ktor" }
+ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers", version.ref = "ktor" }
+ktor-server-call-logging = { module = "io.ktor:ktor-server-call-logging", version.ref = "ktor" }
+ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
+ktor-server-compression = { module = "io.ktor:ktor-server-compression", version.ref = "ktor" }
+ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
+ktor-server-default-headers = { module = "io.ktor:ktor-server-default-headers", version.ref = "ktor" }
+ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
+logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
+plugin-android = { module = "com.android.tools.build:gradle", version.ref = "plugin-android" }
+plugin-compose = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose" }
+plugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" }
+plugin-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
+plugin-git-hooks = { module = "com.github.jakemarsden:git-hooks-gradle-plugin", version.ref = "plugin-git-hooks" }
+plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+plugin-kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
+plugin-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
+plugin-shadow = { module = "gradle.plugin.com.github.jengelman.gradle.plugins:shadow", version.ref = "plugin-shadow" }
+plugin-versions = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "plugin-versions" }
+plugin-versions-update = { module = "nl.littlerobots.vcu:plugin", version.ref = "plugin-versions-update" }
+
+[plugins]
+build-config = { id = "com.github.gmazzo.buildconfig", version.ref = "build-config" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-js = { id = "org.jetbrains.kotlin.js", version.ref = "kotlin" }
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+versions = { id = "com.github.ben-manes.versions", version.ref = "plugin-versions" }
+versions-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "plugin-versions-update" }
diff --git a/gradle/versions.properties b/gradle/versions.properties
new file mode 100644
index 0000000..12075b5
--- /dev/null
+++ b/gradle/versions.properties
@@ -0,0 +1,81 @@
+#### Dependencies and Plugin versions with their available updates.
+#### Generated by `./gradlew refreshVersions` version 0.51.0
+####
+#### Don't manually edit or split the comments that start with four hashtags (####),
+#### they will be overwritten by refreshVersions.
+####
+#### suppress inspection "SpellCheckingInspection" for whole file
+#### suppress inspection "UnusedProperty" for whole file
+
+#======================================================= Plugins =======================================================
+plugin.android=7.3.1
+
+version.androidx.activity=1.6.1
+## # available=1.7.0-alpha01
+## # available=1.7.0-alpha02
+## # available=1.7.0-alpha03
+## # available=1.7.0-alpha04
+## # available=1.7.0-beta01
+## # available=1.8.0-alpha01
+
+version.androidx.appcompat=1.6.1
+## # available=1.7.0-alpha01
+## # available=1.7.0-alpha02
+
+version.androidx.core=1.9.0
+## # available=1.10.0-alpha01
+## # available=1.10.0-alpha02
+
+version.androidx.test.espresso=3.5.1
+
+version.androidx.test.ext.junit=1.1.5
+
+version.com.github.jakemarsden..git-hooks-gradle-plugin=0.0.2
+
+version.detekt=1.22.0
+
+version.gradle.plugin.com.github.jengelman.gradle.plugins..shadow=7.0.0
+
+ version.klip=0.4.1
+
+version.org.jetbrains.compose..compose-gradle-plugin=1.3.0
+
+version.detekt-formatting=1.22.0
+
+#====================================================== Libraries ======================================================
+version.com.github.ajalt.clikt..clikt=3.5.1
+
+version.kotlinx.coroutines=1.6.4
+
+version.app.softwork..routing-compose=0.2.11
+
+ version.kmdc=0.1.0
+
+version.kmongo=4.8.0
+
+version.kodein.di=7.18.0
+
+version.kotest=5.5.5
+
+version.kotlin=1.8.0
+
+ version.ktor=2.2.3
+
+version.org.jetbrains.kotlinx..kover=0.6.1
+
+version.org.slf4j..slf4j-api=2.0.6
+
+version.ch.qos.logback..logback-classic=1.4.5
+
+version.redux-kotlin=0.6.0
+
+version.kotlinx.serialization=1.4.1
+
+version.org.jsoup..jsoup=1.15.3
+
+version.com.microsoft.azure..applicationinsights-web-auto=2.6.4
+
+#========================================================= NPM =========================================================
+version.npm.fontawesome.core=^6.1.0
+
+version.npm.fontawesome=^5.15.4
diff --git a/gradle/versions.rules b/gradle/versions.rules
new file mode 100644
index 0000000..006e2f9
--- /dev/null
+++ b/gradle/versions.rules
@@ -0,0 +1,10 @@
+org.reduxkotlin:redux-kotlin(-*)
+ ^^^^^^^^^^^^
+org.litote.kmongo:kmongo(-*)
+ ^^^^^^
+dev.petuska:klip(-*)
+ ^^^^
+dev.petuska:kmdc(-*)
+ ^^^^
+io.gitlab.arturbosch.detekt:detekt(-*)
+ ^^^^^^
diff --git a/gradle/webpack.config.d/files.js b/gradle/webpack.config.d/files.js
new file mode 100644
index 0000000..ee9db83
--- /dev/null
+++ b/gradle/webpack.config.d/files.js
@@ -0,0 +1,16 @@
+config.module.rules.push(
+ {
+ test: /\.(jpe?g|png|gif|svg)$/i,
+ type: 'asset/resource',
+ generator: {
+ filename: "images/[hash][ext][query]",
+ }
+ },
+ {
+ test: /\.(woff|woff2|eot|ttf|otf)$/,
+ type: 'asset/resource',
+ generator: {
+ filename: "webfonts/[hash][ext][query]",
+ }
+ }
+);
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 41d9927..249e583 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 69a9715..b93c46a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1b6c787..a69d9cb 100755
--- a/gradlew
+++ b/gradlew
@@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index 107acd3..f127cfd 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/infra/.terraform.lock.hcl b/infra/.terraform.lock.hcl
index 502f0e8..538ab15 100644
--- a/infra/.terraform.lock.hcl
+++ b/infra/.terraform.lock.hcl
@@ -2,22 +2,22 @@
# Manual edits may be lost in future updates.
provider "registry.terraform.io/cloudflare/cloudflare" {
- version = "2.26.1"
+ version = "2.25.0"
hashes = [
- "h1:P1ktP+9KnBoqJUnoGbGzOVRbMef1jXL282sJIJ5KlS4=",
- "zh:0fc9e723b7f012870b32f82c094794b5c5debde00d5dc48dbd25beb8e8117171",
- "zh:19a1eb85da263488f766cd2b0e30a4cebb8416c8f928fcb89df3dd12bf6dabef",
- "zh:1fc097f4d471c553e2effc547d0af07d8d1cfb8cf70b0a1e9c7b87db28d823bc",
- "zh:6a33b565397501a6c21095cf773418311a1c192aa8fc1389d258583417b8da8a",
- "zh:6f0b9f88c150d01d30a8c3aa5fe77183a4e43ff344e4b5efec2f12a422222e72",
- "zh:8bf32bbcd88f9078c2b06d5d260cba69da641f2df359ab2e6bd953c6de01610f",
- "zh:91f82ad1e3a880399d1e866200304d259fd599f02c086ddb62402b5501f1ec73",
- "zh:aa0d76d7a3d541452228a40b36947c94cf3481411e80327d70b7a3b4f62eb097",
- "zh:acc4703c44122228c1dd8cda1b6b051d53d6433c708c1bf7f9c27faa6371f8df",
- "zh:d05eb3a268cdb656def53db47fd4e623b060944aa4865a3f2bbf645fdbe7d102",
- "zh:f6032d4a3ef0fe6c034c59d0c75134bc738c5a6f9e16cc9fac9649633b94f674",
- "zh:fa626e82dbee19f997a4e5d1d6b3ddf27c91fee54b219f50ec91fea9a15c7508",
- "zh:fbf97c0025baa0fb7937ccc0782158c8531b9c965b32a4bc8930418e5520c6a8",
+ "h1:raEhY+rMHKBNMwXBWYZp4DUua3JNDq5frLu6VOl71Zg=",
+ "zh:0c2e4665615fa732cd7a025599afa788cf980d1e3e89b20127f8dd5198a3e124",
+ "zh:1d09a74f6ee533ae9db4064a9a6ec466ca7e7f53ed2033f95ce66ff066becc9e",
+ "zh:21f9c1b5d30dc691909b4d1f4635171dc53009109cd7b15ac3ee9c23d84dcc10",
+ "zh:332cbf7da45231fda97d4ca9d840d70558055b45fb63247f57717572c09edde4",
+ "zh:4056c8fd7b6ca9f2b96a583ee11e3ee5e11ec0706a4f5fa1816f2bacda359a31",
+ "zh:5a6e134331acc5c9833993f98cbe48e05c995b8d09ac5d4a11fe4c1488fa34ed",
+ "zh:71c30ce22b906f9130b7d1afe6ca5be281f7dda1b46c6565b9acd22ca3e30fcb",
+ "zh:83e53ed8489a8c65d8e39e43ba5c8298b19eba0dfa30c0d2f99d69e81e6516af",
+ "zh:89900baa4735eb9c678815689979765653ff639a528ac0bbe3fceee50028bea8",
+ "zh:8ba0ea263f0e04589ec95de2381913e6a3b71d7b67c2e2ddbdd78a023ce5949f",
+ "zh:a98c952cda50a7286e8286697b195d1dc8c016090023b4b8cd6f772734b7fd71",
+ "zh:c670798e60fd4807524bf7d407ad284c5674b32f42e56005f63cd356233a2310",
+ "zh:dadd6787f390379f7f231e1176d24ff7841ac9c6b1e742c6bf506309492f89e1",
]
}
diff --git a/infra/main.tf b/infra/main.tf
index ff9ab9d..5401652 100644
--- a/infra/main.tf
+++ b/infra/main.tf
@@ -4,7 +4,7 @@ terraform {
// export ARTIFACTORY_PASSWORD=xxx
url = "https://mpetuska.jfrog.io/artifactory"
repo = "terraform-state"
- subpath = "kamp"
+ subpath = "kodex"
}
required_providers {
mongodbatlas = {
@@ -33,20 +33,20 @@ provider "cloudflare" {
// export CLOUDFLARE_API_TOKEN=xxx
}
-resource "mongodbatlas_project" "kamp" {
- name = "kamp"
+resource "mongodbatlas_project" "kodex" {
+ name = "kodex"
org_id = "60533df97f4d234d9e691ce6"
}
-data "mongodbatlas_cluster" "kamp" {
- project_id = mongodbatlas_project.kamp.id
- name = "kamp"
+data "mongodbatlas_cluster" "kodex" {
+ project_id = mongodbatlas_project.kodex.id
+ name = "kodex"
}
resource "mongodbatlas_database_user" "admin" {
- username = "kamp-admin"
- password = uuidv5("oid", "${data.mongodbatlas_cluster.kamp.id}-admin")
- project_id = mongodbatlas_project.kamp.id
+ username = "kodex-admin"
+ password = uuidv5("oid", "${data.mongodbatlas_cluster.kodex.id}-admin")
+ project_id = mongodbatlas_project.kodex.id
auth_database_name = "admin"
roles {
@@ -60,9 +60,9 @@ resource "mongodbatlas_database_user" "admin" {
}
resource "mongodbatlas_database_user" "reader" {
- username = "kamp-reader"
- password = uuidv5("oid", "${data.mongodbatlas_cluster.kamp.id}-reader")
- project_id = mongodbatlas_project.kamp.id
+ username = "kodex-reader"
+ password = uuidv5("oid", "${data.mongodbatlas_cluster.kodex.id}-reader")
+ project_id = mongodbatlas_project.kodex.id
auth_database_name = "admin"
roles {
@@ -71,22 +71,22 @@ resource "mongodbatlas_database_user" "reader" {
}
}
-resource "azurerm_resource_group" "kamp" {
+resource "azurerm_resource_group" "kodex" {
location = "West Europe"
- name = "kamp"
+ name = "kodex"
}
-resource "azurerm_application_insights" "kamp" {
- name = azurerm_resource_group.kamp.name
- location = azurerm_resource_group.kamp.location
- resource_group_name = azurerm_resource_group.kamp.name
+resource "azurerm_application_insights" "kodex" {
+ name = azurerm_resource_group.kodex.name
+ location = azurerm_resource_group.kodex.location
+ resource_group_name = azurerm_resource_group.kodex.name
application_type = "java"
}
-resource "azurerm_static_site" "kamp" {
- name = azurerm_resource_group.kamp.name
- resource_group_name = azurerm_resource_group.kamp.name
- location = azurerm_resource_group.kamp.location
+resource "azurerm_static_site" "kodex" {
+ name = azurerm_resource_group.kodex.name
+ resource_group_name = azurerm_resource_group.kodex.name
+ location = azurerm_resource_group.kodex.location
}
data "cloudflare_zones" "petuska_dev" {
@@ -97,8 +97,8 @@ data "cloudflare_zones" "petuska_dev" {
resource "cloudflare_record" "www" {
zone_id = data.cloudflare_zones.petuska_dev.zones[0].id
- name = "www.kamp"
- value = azurerm_static_site.kamp.default_host_name
+ name = "www.kodex"
+ value = azurerm_static_site.kodex.default_host_name
type = "CNAME"
proxied = false
ttl = 1
@@ -106,17 +106,17 @@ resource "cloudflare_record" "www" {
resource "cloudflare_record" "root" {
zone_id = data.cloudflare_zones.petuska_dev.zones[0].id
- name = "kamp"
- value = "www.kamp.petuska.dev"
+ name = "kodex"
+ value = "www.kodex.petuska.dev"
type = "CNAME"
proxied = true
ttl = 1
}
-resource "azurerm_app_service_plan" "kamp" {
- location = azurerm_resource_group.kamp.location
- name = azurerm_resource_group.kamp.name
- resource_group_name = azurerm_resource_group.kamp.name
+resource "azurerm_app_service_plan" "kodex" {
+ location = azurerm_resource_group.kodex.location
+ name = azurerm_resource_group.kodex.name
+ resource_group_name = azurerm_resource_group.kodex.name
kind = "Linux"
reserved = true
sku {
@@ -128,18 +128,18 @@ resource "azurerm_app_service_plan" "kamp" {
locals {
database_admin_credentials = "${mongodbatlas_database_user.admin.username}:${mongodbatlas_database_user.admin.password}"
database_reader_credentials = "${mongodbatlas_database_user.reader.username}:${mongodbatlas_database_user.reader.password}"
- connection_string_chunks = split("//", data.mongodbatlas_cluster.kamp.connection_strings[0].standard_srv)
+ connection_string_chunks = split("//", data.mongodbatlas_cluster.kodex.connection_strings[0].standard_srv)
connection_string_options = "ssl=true&retryWrites=true&w=majority"
database_admin_connection_string = "${local.connection_string_chunks[0]}//${local.database_admin_credentials}@${local.connection_string_chunks[1]}/${local.database_name}?${local.connection_string_options}"
database_reader_connection_string = "${local.connection_string_chunks[0]}//${local.database_reader_credentials}@${local.connection_string_chunks[1]}/${local.database_name}?${local.connection_string_options}"
- database_name = "kamp"
+ database_name = "kodex"
}
-resource "azurerm_app_service" "kamp" {
- name = azurerm_app_service_plan.kamp.name
- location = azurerm_app_service_plan.kamp.location
- resource_group_name = azurerm_app_service_plan.kamp.resource_group_name
- app_service_plan_id = azurerm_app_service_plan.kamp.id
+resource "azurerm_app_service" "kodex" {
+ name = azurerm_app_service_plan.kodex.name
+ location = azurerm_app_service_plan.kodex.location
+ resource_group_name = azurerm_app_service_plan.kodex.resource_group_name
+ app_service_plan_id = azurerm_app_service_plan.kodex.id
https_only = true
site_config {
@@ -171,8 +171,8 @@ resource "azurerm_app_service" "kamp" {
MONGO_DATABASE = local.database_name
ADMIN_USER = var.api_admin_user
ADMIN_PASSWORD = var.api_admin_password
- AZURE_MONITOR_INSTRUMENTATION_KEY = azurerm_application_insights.kamp.instrumentation_key
- APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.kamp.instrumentation_key
+ AZURE_MONITOR_INSTRUMENTATION_KEY = azurerm_application_insights.kodex.instrumentation_key
+ APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.kodex.instrumentation_key
APPINSIGHTS_PROFILERFEATURE_VERSION = "1.0.0"
JVM_ARGS = ""
}
diff --git a/infra/outputs.tf b/infra/outputs.tf
index 0a175b1..c8ff7e4 100644
--- a/infra/outputs.tf
+++ b/infra/outputs.tf
@@ -1,32 +1,32 @@
output "app_service_name" {
- value = azurerm_app_service.kamp.name
+ value = azurerm_app_service.kodex.name
}
output "app_service_default_hostname" {
- value = "https://${azurerm_app_service.kamp.default_site_hostname}"
+ value = "https://${azurerm_app_service.kodex.default_site_hostname}"
}
output "app_service_static_app_api_token" {
- value = azurerm_static_site.kamp.api_key
+ value = azurerm_static_site.kodex.api_key
sensitive = true
}
-output "mongodbatlas_kamp_reader_credentials" {
+output "mongodbatlas_kodex_reader_credentials" {
value = local.database_reader_credentials
sensitive = true
}
-output "mongodbatlas_kamp_reader_connection_string" {
+output "mongodbatlas_kodex_reader_connection_string" {
value = local.database_reader_connection_string
sensitive = true
}
-output "mongodbatlas_kamp_admin_credentials" {
+output "mongodbatlas_kodex_admin_credentials" {
value = local.database_admin_credentials
sensitive = true
}
-output "mongodbatlas_kamp_admin_connection_string" {
+output "mongodbatlas_kodex_admin_connection_string" {
value = local.database_admin_connection_string
sensitive = true
}
diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock
new file mode 100644
index 0000000..ac5bab7
--- /dev/null
+++ b/kotlin-js-store/yarn.lock
@@ -0,0 +1,3936 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/code-frame@^7.10.4":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
+ integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==
+ dependencies:
+ "@babel/highlight" "^7.22.5"
+
+"@babel/helper-validator-identifier@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
+ integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
+
+"@babel/highlight@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031"
+ integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.5"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@colors/colors@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
+ integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
+
+"@discoveryjs/json-ext@^0.5.0":
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
+ integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
+
+"@jridgewell/gen-mapping@^0.3.0":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
+ integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@1.4.14":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
+ integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
+ dependencies:
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
+
+"@leichtgewicht/ip-codec@^2.0.1":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
+ integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
+
+"@material/animation@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/animation/-/animation-14.0.0.tgz#f23fbe38deb6a48829dcdb0b7580017a4217e94b"
+ integrity sha512-VlYSfUaIj/BBVtRZI8Gv0VvzikFf+XgK0Zdgsok5c1v5DDnNz5tpB8mnGrveWz0rHbp1X4+CWLKrTwNmjrw3Xw==
+ dependencies:
+ tslib "^2.1.0"
+
+"@material/banner@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/banner/-/banner-14.0.0.tgz#f22163f7df433f8a6239007c98910150a7ec931e"
+ integrity sha512-z0WPBVQxbQVcV1km4hFD40xBEeVWYtCzl2jrkHd8xXexP/fMvXkFU1UfwSWvY3jlWx//j4/Xd7VpnRdEXS4RLQ==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/button" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/base@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/base/-/base-14.0.0.tgz#022debef6762764dbe1056eb1bcffca6b354883c"
+ integrity sha512-Ou7vS7n1H4Y10MUZyYAbt6H0t67c6urxoCgeVT7M38aQlaNUwFMODp7KT/myjYz2YULfhu3PtfSV3Sltgac9mA==
+ dependencies:
+ tslib "^2.1.0"
+
+"@material/button@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/button/-/button-14.0.0.tgz#444402b5c1e31c3b63110fa640985a01102c0675"
+ integrity sha512-dqqHaJq0peyXBZupFzCjmvScrfljyVU66ZCS3oldsaaj5iz8sn33I/45Z4zPzdR5F5z8ExToHkRcXhakj1UEAA==
+ dependencies:
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/card@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/card/-/card-14.0.0.tgz#81a8d201bbfb37cbb4d0b738cb55f23da787c643"
+ integrity sha512-SnpYWUrCb92meGYLXV7qa/k40gnHR6rPki6A1wz0OAyG2twY48f0HLscAqxBLvbbm1LuRaqjz0RLKGH3VzxZHw==
+ dependencies:
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/checkbox@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-14.0.0.tgz#2a28915d297b07052d9286cb429af28c725441b9"
+ integrity sha512-OoqwysCqvj1d0cRmEwVWPvg5OqYAiCFpE6Wng6me/Cahfe4xgRxSPa37WWqsClw20W7PG/5RrYRCBtc6bUUUZA==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/chips@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/chips/-/chips-14.0.0.tgz#ffa6e5434a23fcc961e03827e0ddbbfa395b0def"
+ integrity sha512-SfZX/Ovdq4NgjdtIr/N1O3fEHisZC+t8G8629OV/NrniSS6rKOa+q1mImzna8R4pfuYO+7nT5nZewQpL/JSYaQ==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/checkbox" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/circular-progress@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/circular-progress/-/circular-progress-14.0.0.tgz#95c2859900a0b7dba1f5acb68d6d08923c6c7d2d"
+ integrity sha512-7EdkP6ty54g6qs6zzlsw29vWlUyrcSWr9b4pGGx4D/iNJww+eyxXZ07iWoNOr4uLgguauWEft2axpQiFCwFD0g==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/progress-indicator" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/data-table@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/data-table/-/data-table-14.0.0.tgz#761277759209d528529fd8b296894983b03cd0f9"
+ integrity sha512-tnmLawGaMtnp29KH8pX99bqeKmFODE+MtRUTt6TauupkEfQE/wd0Um4JQDFiI0kCch7uF3r/NmQKyKuan10hXw==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/checkbox" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/icon-button" "^14.0.0"
+ "@material/linear-progress" "^14.0.0"
+ "@material/list" "^14.0.0"
+ "@material/menu" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/select" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/density@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/density/-/density-14.0.0.tgz#584c4f3468c86c96e361ebe7ba6723f4aa6cd548"
+ integrity sha512-NlxXBV5XjNsKd8UXF4K/+fOXLxoFNecKbsaQO6O2u+iG8QBfFreKRmkhEBb2hPPwC3w8nrODwXX0lHV+toICQw==
+ dependencies:
+ tslib "^2.1.0"
+
+"@material/dialog@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/dialog/-/dialog-14.0.0.tgz#d24d1c1441de343c6615bbc5dfedb068f7e86c05"
+ integrity sha512-E07NEE4jP8jHaw/y2Il2R1a3f4wDFh2sgfCBtRO/Xh0xxJUMuQ7YXo/F3SAA8jfMbbkUv/PHdJUM3I3HmI9mAA==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/button" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/icon-button" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/dom@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/dom/-/dom-14.0.0.tgz#dad6908ea26f7cb21b1078f58ec04bd897c615c4"
+ integrity sha512-8t88XyacclTj8qsIw9q0vEj4PI2KVncLoIsIMzwuMx49P2FZg6TsLjor262MI3Qs00UWAifuLMrhnOnfyrbe7Q==
+ dependencies:
+ "@material/feature-targeting" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/drawer@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/drawer/-/drawer-14.0.0.tgz#bd1421bc45a1d8824f8fab7f23893997d01580ef"
+ integrity sha512-VPrxMIhbkXVbfH7aMFV+Um0tjOVrU/Y65X2hWsVdmjASadE8C5UYjIE3vjL1DM1M+zIa3qZZRUWqz0j1zqbr3w==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/list" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/elevation@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-14.0.0.tgz#82018c3f0856ff5057d9f80df43db27b1e19a52b"
+ integrity sha512-Di3tkxTpXwvf1GJUmaC8rd+zVh5dB2SWMBGagL4+kT8UmjSISif/OPRGuGnXs3QhF6nmEjkdC0ijdZLcYQkepw==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/fab@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/fab/-/fab-14.0.0.tgz#9561178ff593157ad07a0841c6e213a06d6fe6ce"
+ integrity sha512-s4rrw2TLU8ITKopHSTEHuJEFsGEZsb+ijwW16pQt0h9GArxPGaALT+CCJIPjf75D3wPEEMW0vnLj7oMoII2VFg==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/feature-targeting@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-14.0.0.tgz#a6a878ae2be1f88d31b0bb95f05dbf5d486c4b3e"
+ integrity sha512-a5WGgHEq5lJeeNL5yevtgoZjBjXWy6+klfVWQEh8oyix/rMJygGgO7gEc52uv8fB8uAIoYEB3iBMOv8jRq8FeA==
+ dependencies:
+ tslib "^2.1.0"
+
+"@material/floating-label@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/floating-label/-/floating-label-14.0.0.tgz#9314f26798090d806f3772360bd694cba19443ce"
+ integrity sha512-Aq8BboP1sbNnOtsV72AfaYirHyOrQ/GKFoLrZ1Jt+ZGIAuXPETcj9z7nQDznst0ZeKcz420PxNn9tsybTbeL/Q==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/focus-ring@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/focus-ring/-/focus-ring-14.0.0.tgz#946c27f9f724e9560b6919676b4be166cfab539b"
+ integrity sha512-fqqka6iSfQGJG3Le48RxPCtnOiaLGPDPikhktGbxlyW9srBVMgeCiONfHM7IT/1eu80O0Y67Lh/4ohu5+C+VAQ==
+ dependencies:
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+
+"@material/form-field@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/form-field/-/form-field-14.0.0.tgz#a1d773f0d8d25cc7c59201ff62d2c11e5cfb7122"
+ integrity sha512-k1GNBj6Sp8A7Xsn5lTMp5DkUkg60HX7YkQIRyFz1qCDCKJRWh/ou7Z45GMMgKmG3aF6LfjIavc7SjyCl8e5yVg==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/icon-button@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-14.0.0.tgz#cdfc7e7b967abe81d537fd7db916c9113c3a09b7"
+ integrity sha512-wHMqzm7Q/UwbWLoWv32Li1r2iVYxadIrwTNxT0+p+7NdfI3lEwMN3NoB0CvoJnHTljjXDzce0KJ3nZloa0P0gA==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/image-list@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/image-list/-/image-list-14.0.0.tgz#56185d799585f79aa85c31e6730139b3b7198c53"
+ integrity sha512-vx/7WCMbiZoy/R+DmO7r0N3jWzFjlvvDMeBpXt0btglWP3EYbVnDqzseW4u1TtY+IBbJldW/DsiCN1oLnlEVxw==
+ dependencies:
+ "@material/feature-targeting" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/layout-grid@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/layout-grid/-/layout-grid-14.0.0.tgz#d70569dfd7d372f0c72d30b8b87f3965a3c6bc72"
+ integrity sha512-tAce0PR/c85VI2gf1HUdM0Y15ZWpfZWAFIwaCRW1+jnOLWnG1/aOJYLlzqtVEv2m0TS1R1WRRGN3Or+CWvpDRA==
+ dependencies:
+ tslib "^2.1.0"
+
+"@material/line-ripple@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/line-ripple/-/line-ripple-14.0.0.tgz#460e972dd99d119b97d3ee4b7a73bc1d199abfad"
+ integrity sha512-Rx9eSnfp3FcsNz4O+fobNNq2PSm5tYHC3hRpY2ZK3ghTvgp3Y40/soaGEi/Vdg0F7jJXRaBSNOe6p5t9CVfy8Q==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/linear-progress@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/linear-progress/-/linear-progress-14.0.0.tgz#c62b4b664b9c815af0d5317b5aa6f1adf461f4e6"
+ integrity sha512-MGIAWMHMW6TSV/TNWyl5N/escpDHk3Rq6hultFif+D9adqbOXrtfZZIFPLj1FpMm1Ucnj6zgOmJHgCDsxRVNIA==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/progress-indicator" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/list@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/list/-/list-14.0.0.tgz#724d8a17b01200217262036ef031489270170c89"
+ integrity sha512-AFaBGV9vQyfnG8BT2R3UGVdF5w2SigQqBH+qbOSxQhk4BgVvhDfJUIKT415poLNMdnaDtcuYz+ZWvVNoRDaL7w==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/menu-surface@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/menu-surface/-/menu-surface-14.0.0.tgz#0cd264f34af42ec2450e7baf08ec51ec6c8c4f7d"
+ integrity sha512-wRz3UCrhJ4kRrijJEbvIPRa0mqA5qkQmKXjBH4Xu1ApedZruP+OM3Qb2Bj4XugCA3eCXpiohg+gdyTAX3dVQyw==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/menu@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/menu/-/menu-14.0.0.tgz#7afad215de05df9b601652c93d344f4cb59eea0c"
+ integrity sha512-oU6GjbYnkG6a5nX9HUSege5OQByf6yUteEij8fpf0ci3f5BWf/gr39dnQ+rfl+q119cW0WIEmVK2YJ/BFxMzEQ==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/list" "^14.0.0"
+ "@material/menu-surface" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/notched-outline@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/notched-outline/-/notched-outline-14.0.0.tgz#b315e2cfaf8226c557f253362ea4167f87a241ce"
+ integrity sha512-6S58DlWmhCDr4RQF2RuwqANxlmLdHtWy2mF4JQLD9WOiCg4qY9eCQnMXu3Tbhr7f/nOZ0vzc7AtA3vfJoZmCSw==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/floating-label" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/progress-indicator@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/progress-indicator/-/progress-indicator-14.0.0.tgz#638e46207746d24e87b7794c0492ee11764481db"
+ integrity sha512-09JRTuIySxs670Tcy4jVlqCUbyrO+Ad6z3nHnAi8pYl74duco4n/9jTROV0mlFdr9NIFifnd08lKbiFLDmfJGQ==
+ dependencies:
+ tslib "^2.1.0"
+
+"@material/radio@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/radio/-/radio-14.0.0.tgz#d80ccde907dd49733f48819a44c699a4472ea59b"
+ integrity sha512-VwPOi5fAoZXL3RhQJ6iDWTR34L6JXlwd5VXli8ZhzNHnUzcmpMODrRhGVew4Z5uuNj6/n2Jbn1zcS9XmmqjssA==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/ripple@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-14.0.0.tgz#4a6b3639197f39d78be967b46195007aedf06652"
+ integrity sha512-9XoGBFd5JhFgELgW7pqtiLy+CnCIcV2s9cQ2BWbOQeA8faX9UZIDUx/g76nHLZ7UzKFtsULJxZTwORmsEt2zvw==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/rtl@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-14.0.0.tgz#6be5adf56dcbab0d4a7dabd9ab724fbcc5c63d6b"
+ integrity sha512-xl6OZYyRjuiW2hmbjV2omMV8sQtfmKAjeWnD1RMiAPLCTyOW9Lh/PYYnXjxUrNa0cRwIIbOn5J7OYXokja8puA==
+ dependencies:
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/segmented-button@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/segmented-button/-/segmented-button-14.0.0.tgz#607140faa6ede2690624ffe8a3e4a25c8e1c8d02"
+ integrity sha512-6es7PPNX3T3h7bOLyb8L38hMoTXqBs5XX8XCKycKZG2Dm4stac/yYMKKO/q3MOn36t37s+JAVTjyRB8HnJu5Gg==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/touch-target" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/select@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/select/-/select-14.0.0.tgz#31879f78779740400354282b41bba6d87fb2bb2e"
+ integrity sha512-4aY1kUHEnbOCRG3Tkuuk8yFfyNYSvOstBbjiYE/Z1ZGF3P1z+ON35iLatP84LvNteX4F1EMO2QAta2QbLRMAkw==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/floating-label" "^14.0.0"
+ "@material/line-ripple" "^14.0.0"
+ "@material/list" "^14.0.0"
+ "@material/menu" "^14.0.0"
+ "@material/menu-surface" "^14.0.0"
+ "@material/notched-outline" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/shape@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/shape/-/shape-14.0.0.tgz#b58f39d743394c2ff7c57f0f004f0aabade2779e"
+ integrity sha512-o0mJB0+feOv473KckI8gFnUo8IQAaEA6ynXzw3VIYFjPi48pJwrxa0mZcJP/OoTXrCbDzDeFJfDPXEmRioBb9A==
+ dependencies:
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/slider@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/slider/-/slider-14.0.0.tgz#8a1e4ac14b2831e53c5e97237219005060a67eea"
+ integrity sha512-m5RqySIps1vhAQnGp2eg4Sh2Ss6bzrZm10TWBw2cNFHmbiI72rK2EeFnMsBXAarplY0cot/FaMuj91VP36gKFQ==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/snackbar@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/snackbar/-/snackbar-14.0.0.tgz#eebea965d2090d27e290c3a1497d9fcd89ddc968"
+ integrity sha512-28uQBj9bw7BalNarK9j8/aVW4Ys5aRaGHoWH+CeYvAjqQUJkrYoqM52aiKhBwqrjBPMJHk1aXthe3YbzMBm6vA==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/button" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/icon-button" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/switch@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/switch/-/switch-14.0.0.tgz#d302a8786ad7f8809d9bbaf416dcceefb98e833f"
+ integrity sha512-vHVKzbvHVKGSrkMB1lZAl8z3eJ8sPRnSR+DWn+IhqHcTsDdDyly2NNj4i2vTSrEA39CztGqkx0OnKM4vkpiZHw==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/tab-bar@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/tab-bar/-/tab-bar-14.0.0.tgz#2030bb19962669eebbe74b3ad3ea996d1cc53175"
+ integrity sha512-G/UYEOIcljCHlkj3iCRGIz4zE9RVcsdC9wuOR6LE2rla6EGyT0x2psNlL0pIMROjXoB0HGda/gB90ovzKcbURA==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/tab" "^14.0.0"
+ "@material/tab-indicator" "^14.0.0"
+ "@material/tab-scroller" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/tab-indicator@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/tab-indicator/-/tab-indicator-14.0.0.tgz#e55e5bf9fe1c52987558c2628d02d8f0c5c894e6"
+ integrity sha512-wfq136fsJGqtCIW8x1wFQHgRr7dIQ9SWqp6WG4FQGHpSzliNDA23/bdBUjh3lX2U+mfbdsFmZWEPy06jg2uc5g==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/tab-scroller@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/tab-scroller/-/tab-scroller-14.0.0.tgz#7f23de03de5449f4d54c0f1ed00950c6682e1c4a"
+ integrity sha512-wadETsRM7vT4mRjXedaPXxI/WFSSgqHRNI//dORJ6627hoiJfLb5ixwUKTYk9zTz6gNwAlRTrKh98Dr9T7n7Kw==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/tab" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/tab@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/tab/-/tab-14.0.0.tgz#8b6be7eaacbb1b1e8b9d159a446d0a19bf448cf1"
+ integrity sha512-jGSQdp6BvZOVnvGbv0DvNDJL2lHYVFtKGehV0gSZ7FrjHK6gZnKZjWOVwt1NPu9ig9zy85vPRFpvFTeje1KZpg==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/focus-ring" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/tab-indicator" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/textfield@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/textfield/-/textfield-14.0.0.tgz#8ce939ef38a083104e4585b8ebf25c840c1c40b6"
+ integrity sha512-HGbtAlvlIB2vWBq85yw5wQeeP3Kndl6Z0TJzQ6piVtcfdl2mPyWhuuVHQRRAOis3rCIaAAaxCQYYTJh8wIi0XQ==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/density" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/floating-label" "^14.0.0"
+ "@material/line-ripple" "^14.0.0"
+ "@material/notched-outline" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/tokens" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/theme@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/theme/-/theme-14.0.0.tgz#cbcd7b2116220c44ef8a89c43b068b29d23456ea"
+ integrity sha512-6/SENWNIFuXzeHMPHrYwbsXKgkvCtWuzzQ3cUu4UEt3KcQ5YpViazIM6h8ByYKZP8A9d8QpkJ0WGX5btGDcVoA==
+ dependencies:
+ "@material/feature-targeting" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/tokens@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/tokens/-/tokens-14.0.0.tgz#67cdc2530cecda8509867e941249b7d4778400a7"
+ integrity sha512-SXgB9VwsKW4DFkHmJfDIS0x0cGdMWC1D06m6z/WQQ5P5j6/m0pKrbHVlrLzXcRjau+mFhXGvj/KyPo9Pp/Rc8Q==
+ dependencies:
+ "@material/elevation" "^14.0.0"
+
+"@material/tooltip@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/tooltip/-/tooltip-14.0.0.tgz#16bc9277bd347e581c0fd23da29ef9ff3a463431"
+ integrity sha512-rp7sOuVE1hmg4VgBJMnSvtDbSzctL42X7y1yv8ukuu40Sli+H5FT0Zbn351EfjJgQWg/AlXA6+reVXkXje8JzQ==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/dom" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/top-app-bar@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/top-app-bar/-/top-app-bar-14.0.0.tgz#54e09247c33f61f8f3e98016990e53102c4a4410"
+ integrity sha512-uPej5vHgZnlSB1+koiA9FnabXrHh3O/Npl2ifpUgDVwHDSOxKvLp2LNjyCO71co1QLNnNHIU0xXv3B97Gb0rpA==
+ dependencies:
+ "@material/animation" "^14.0.0"
+ "@material/base" "^14.0.0"
+ "@material/elevation" "^14.0.0"
+ "@material/ripple" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ "@material/shape" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ "@material/typography" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/touch-target@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/touch-target/-/touch-target-14.0.0.tgz#66b0b61ff14975946cdbf6fad6627bcbc025423d"
+ integrity sha512-o3kvxmS4HkmZoQTvtzLJrqSG+ezYXkyINm3Uiwio1PTg67pDgK5FRwInkz0VNaWPcw9+5jqjUQGjuZMtjQMq8w==
+ dependencies:
+ "@material/base" "^14.0.0"
+ "@material/feature-targeting" "^14.0.0"
+ "@material/rtl" "^14.0.0"
+ tslib "^2.1.0"
+
+"@material/typography@^14.0.0":
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/@material/typography/-/typography-14.0.0.tgz#a13ffc47eaeaa09852f84b7e1206a69b5c9dbc89"
+ integrity sha512-/QtHBYiTR+TPMryM/CT386B2WlAQf/Ae32V324Z7P40gHLKY/YBXx7FDutAWZFeOerq/two4Nd2aAHBcMM2wMw==
+ dependencies:
+ "@material/feature-targeting" "^14.0.0"
+ "@material/theme" "^14.0.0"
+ tslib "^2.1.0"
+
+"@rollup/plugin-commonjs@^21.0.1":
+ version "21.1.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.1.0.tgz#45576d7b47609af2db87f55a6d4b46e44fc3a553"
+ integrity sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ commondir "^1.0.1"
+ estree-walker "^2.0.1"
+ glob "^7.1.6"
+ is-reference "^1.2.1"
+ magic-string "^0.25.7"
+ resolve "^1.17.0"
+
+"@rollup/plugin-node-resolve@^13.1.3":
+ version "13.3.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c"
+ integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ "@types/resolve" "1.17.1"
+ deepmerge "^4.2.2"
+ is-builtin-module "^3.1.0"
+ is-module "^1.0.0"
+ resolve "^1.19.0"
+
+"@rollup/plugin-typescript@^8.3.0":
+ version "8.5.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.5.0.tgz#7ea11599a15b0a30fa7ea69ce3b791d41b862515"
+ integrity sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ resolve "^1.17.0"
+
+"@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
+ integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
+ dependencies:
+ "@types/estree" "0.0.39"
+ estree-walker "^1.0.1"
+ picomatch "^2.2.2"
+
+"@socket.io/component-emitter@~3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
+ integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
+
+"@types/body-parser@*":
+ version "1.19.2"
+ resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
+ integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==
+ dependencies:
+ "@types/connect" "*"
+ "@types/node" "*"
+
+"@types/bonjour@^3.5.9":
+ version "3.5.10"
+ resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275"
+ integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/connect-history-api-fallback@^1.3.5":
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae"
+ integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==
+ dependencies:
+ "@types/express-serve-static-core" "*"
+ "@types/node" "*"
+
+"@types/connect@*":
+ version "3.4.35"
+ resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
+ integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
+ dependencies:
+ "@types/node" "*"
+
+"@types/cookie@^0.4.1":
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
+ integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
+
+"@types/cors@^2.8.12":
+ version "2.8.12"
+ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
+ integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
+
+"@types/eslint-scope@^3.7.3":
+ version "3.7.3"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224"
+ integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "8.4.2"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.2.tgz#48f2ac58ab9c631cb68845c3d956b28f79fad575"
+ integrity sha512-Z1nseZON+GEnFjJc04sv4NSALGjhFwy6K0HXt7qsn5ArfAKtb63dXNJHf+1YW6IpOIYRBGUbu3GwJdj8DGnCjA==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*", "@types/estree@^0.0.51":
+ version "0.0.51"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
+ integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
+
+"@types/estree@0.0.39":
+ version "0.0.39"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+ integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+
+"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18":
+ version "4.17.29"
+ resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz#2a1795ea8e9e9c91b4a4bbe475034b20c1ec711c"
+ integrity sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+
+"@types/express@*", "@types/express@^4.17.13":
+ version "4.17.13"
+ resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
+ integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "^4.17.18"
+ "@types/qs" "*"
+ "@types/serve-static" "*"
+
+"@types/http-proxy@^1.17.8":
+ version "1.17.9"
+ resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a"
+ integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+ version "7.0.11"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
+ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
+
+"@types/mime@*":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
+ integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
+
+"@types/mime@^1":
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
+ integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
+
+"@types/node@*", "@types/node@>=10.0.0":
+ version "17.0.36"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.36.tgz#c0d5f2fe76b47b63e0e0efc3d2049a9970d68794"
+ integrity sha512-V3orv+ggDsWVHP99K3JlwtH20R7J4IhI1Kksgc+64q5VxgfRkQG8Ws3MFm/FZOKDYGy9feGFlZ70/HpCNe9QaA==
+
+"@types/node@^12.12.14":
+ version "12.20.55"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
+ integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
+
+"@types/qs@*":
+ version "6.9.7"
+ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
+ integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
+
+"@types/range-parser@*":
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
+ integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
+
+"@types/resolve@1.17.1":
+ version "1.17.1"
+ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
+ integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/retry@0.12.0":
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
+ integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
+
+"@types/serve-index@^1.9.1":
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278"
+ integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==
+ dependencies:
+ "@types/express" "*"
+
+"@types/serve-static@*":
+ version "1.13.10"
+ resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
+ integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==
+ dependencies:
+ "@types/mime" "^1"
+ "@types/node" "*"
+
+"@types/serve-static@^1.13.10":
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155"
+ integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==
+ dependencies:
+ "@types/mime" "*"
+ "@types/node" "*"
+
+"@types/sockjs@^0.3.33":
+ version "0.3.33"
+ resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f"
+ integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/ws@^8.5.1":
+ version "8.5.3"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
+ integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==
+ dependencies:
+ "@types/node" "*"
+
+"@ungap/promise-all-settled@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
+ integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
+
+"@webassemblyjs/ast@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
+ integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==
+ dependencies:
+ "@webassemblyjs/helper-numbers" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+
+"@webassemblyjs/floating-point-hex-parser@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f"
+ integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==
+
+"@webassemblyjs/helper-api-error@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16"
+ integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==
+
+"@webassemblyjs/helper-buffer@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5"
+ integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==
+
+"@webassemblyjs/helper-numbers@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae"
+ integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==
+ dependencies:
+ "@webassemblyjs/floating-point-hex-parser" "1.11.1"
+ "@webassemblyjs/helper-api-error" "1.11.1"
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/helper-wasm-bytecode@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1"
+ integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==
+
+"@webassemblyjs/helper-wasm-section@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a"
+ integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+
+"@webassemblyjs/ieee754@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614"
+ integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==
+ dependencies:
+ "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5"
+ integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==
+ dependencies:
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff"
+ integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==
+
+"@webassemblyjs/wasm-edit@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6"
+ integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/helper-wasm-section" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+ "@webassemblyjs/wasm-opt" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+ "@webassemblyjs/wast-printer" "1.11.1"
+
+"@webassemblyjs/wasm-gen@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76"
+ integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/ieee754" "1.11.1"
+ "@webassemblyjs/leb128" "1.11.1"
+ "@webassemblyjs/utf8" "1.11.1"
+
+"@webassemblyjs/wasm-opt@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2"
+ integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+
+"@webassemblyjs/wasm-parser@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199"
+ integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-api-error" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/ieee754" "1.11.1"
+ "@webassemblyjs/leb128" "1.11.1"
+ "@webassemblyjs/utf8" "1.11.1"
+
+"@webassemblyjs/wast-printer@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0"
+ integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@xtuc/long" "4.2.2"
+
+"@webpack-cli/configtest@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5"
+ integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==
+
+"@webpack-cli/info@^1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1"
+ integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==
+ dependencies:
+ envinfo "^7.7.3"
+
+"@webpack-cli/serve@^1.7.0":
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1"
+ integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==
+
+"@xtuc/ieee754@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
+ integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
+
+"@xtuc/long@4.2.2":
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+ integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+
+abab@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
+ integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
+
+abort-controller@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
+ integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
+ dependencies:
+ event-target-shim "^5.0.0"
+
+accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+ dependencies:
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
+
+acorn-import-assertions@^1.7.6:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
+ integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
+
+acorn@^8.5.0:
+ version "8.7.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
+ integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
+
+acorn@^8.7.1:
+ version "8.8.2"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
+ integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
+
+acorn@^8.8.2:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+ integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
+ajv-formats@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
+ integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+ dependencies:
+ ajv "^8.0.0"
+
+ajv-keywords@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
+ integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
+
+ajv-keywords@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
+ integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+
+ajv@^6.12.5:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ 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"
+
+ajv@^8.0.0, ajv@^8.8.0:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
+ integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
+ansi-colors@4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+ integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
+ansi-html-community@^0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
+ integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+anymatch@~3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
+ integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+ integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
+
+array-flatten@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
+ integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
+
+atob@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
+ integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+base64id@2.0.0, base64id@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
+ integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
+
+batch@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
+ integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
+
+binary-extensions@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+body-parser@1.20.1:
+ version "1.20.1"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
+ integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
+ 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"
+
+body-parser@^1.19.0:
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5"
+ integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
+ 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.10.3"
+ raw-body "2.5.1"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
+bonjour-service@^1.0.11:
+ version "1.0.14"
+ resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.14.tgz#c346f5bc84e87802d08f8d5a60b93f758e514ee7"
+ integrity sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==
+ dependencies:
+ array-flatten "^2.1.2"
+ dns-equal "^1.0.0"
+ fast-deep-equal "^3.1.3"
+ multicast-dns "^7.2.5"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@^3.0.2, braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browser-stdout@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+ integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+
+browserslist@^4.14.5:
+ version "4.20.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf"
+ integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
+ dependencies:
+ caniuse-lite "^1.0.30001332"
+ electron-to-chromium "^1.4.118"
+ escalade "^3.1.1"
+ node-releases "^2.0.3"
+ picocolors "^1.0.0"
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+builtin-modules@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
+ integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
+
+bytes@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+ integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
+
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+call-bind@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+ integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.2"
+
+camelcase@^6.0.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+ integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+caniuse-lite@^1.0.30001332:
+ version "1.0.30001344"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz#8a1e7fdc4db9c2ec79a05e9fd68eb93a761888bb"
+ integrity sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g==
+
+chalk@^2.0.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+chalk@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1, chokidar@^3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ 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"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+chrome-trace-event@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
+ integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
+
+cliui@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+ integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
+clone-deep@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+ integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
+ dependencies:
+ is-plain-object "^2.0.4"
+ kind-of "^6.0.2"
+ shallow-clone "^3.0.0"
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colorette@^2.0.10, colorette@^2.0.14:
+ version "2.0.16"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
+ integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==
+
+commander@^2.20.0:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
+ integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+ integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
+
+compressible@~2.0.16:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
+ integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
+ dependencies:
+ mime-db ">= 1.43.0 < 2"
+
+compression@^1.7.4:
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
+ integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
+ 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"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+connect-history-api-fallback@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
+ integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
+
+connect@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8"
+ integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==
+ dependencies:
+ debug "2.6.9"
+ finalhandler "1.1.2"
+ parseurl "~1.3.3"
+ utils-merge "1.0.1"
+
+content-disposition@0.5.4:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+ integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+ dependencies:
+ safe-buffer "5.2.1"
+
+content-type@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+ integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+ integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
+
+cookie@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
+ integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+
+cookie@~0.4.1:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
+ integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
+
+core-util-is@~1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+ integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
+cors@~2.8.5:
+ version "2.8.5"
+ resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
+ integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+ dependencies:
+ object-assign "^4"
+ vary "^1"
+
+cross-spawn@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+css-loader@6.7.1:
+ version "6.7.1"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e"
+ integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==
+ dependencies:
+ icss-utils "^5.1.0"
+ postcss "^8.4.7"
+ postcss-modules-extract-imports "^3.0.0"
+ postcss-modules-local-by-default "^4.0.0"
+ postcss-modules-scope "^3.0.0"
+ postcss-modules-values "^4.0.0"
+ postcss-value-parser "^4.2.0"
+ semver "^7.3.5"
+
+cssesc@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
+ integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+
+custom-event@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
+ integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==
+
+date-format@^4.0.10:
+ version "4.0.10"
+ resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.10.tgz#7aa4bc0ad0c79f4ba858290e5dbb47f27d602e79"
+ integrity sha512-RuMIHocrVjF84bUSTcd1uokIsLsOsk1Awb7TexNOI3f48ukCu39mjslWquDTA08VaDMF2umr3MB9ow5EyJTWyA==
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@4.3.4, debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+decamelize@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
+ integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
+
+decode-uri-component@^0.2.0:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
+ integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
+
+deepmerge@^4.2.2:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
+ integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
+
+default-gateway@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71"
+ integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==
+ dependencies:
+ execa "^5.0.0"
+
+define-lazy-prop@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
+ integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
+
+depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+depd@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+ integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
+
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+detect-node@^2.0.4:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
+ integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
+
+di@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
+ integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==
+
+diff@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+ integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
+dns-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
+ integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==
+
+dns-packet@^5.2.2:
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b"
+ integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==
+ dependencies:
+ "@leichtgewicht/ip-codec" "^2.0.1"
+
+dom-serialize@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
+ integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==
+ dependencies:
+ custom-event "~1.0.0"
+ ent "~2.2.0"
+ extend "^3.0.0"
+ void-elements "^2.0.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+electron-to-chromium@^1.4.118:
+ version "1.4.141"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.141.tgz#4dd9119e8a99f1c83c51dfcf1bed79ea541f08d6"
+ integrity sha512-mfBcbqc0qc6RlxrsIgLG2wCqkiPAjEezHxGTu7p3dHHFOurH4EjS9rFZndX5axC8264rI1Pcbw8uQP39oZckeA==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+engine.io-parser@~5.0.3:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0"
+ integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
+
+engine.io@~6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0"
+ integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==
+ 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.0.3"
+ ws "~8.2.3"
+
+enhanced-resolve@^5.10.0:
+ version "5.12.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634"
+ integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+ent@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
+ integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==
+
+envinfo@^7.7.3:
+ version "7.8.1"
+ resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
+ integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
+
+es-module-lexer@^0.9.0:
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19"
+ integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+escape-string-regexp@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+eslint-scope@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^4.1.1"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^4.1.1:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+ integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estree-walker@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
+ integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
+
+estree-walker@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+
+event-target-shim@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
+ integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
+
+eventemitter3@^4.0.0:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+ integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+events@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
+ integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+
+execa@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
+ integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+ 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"
+
+express@^4.17.3:
+ version "4.18.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
+ integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
+ 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"
+
+extend@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fastest-levenshtein@^1.0.12:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
+ integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
+
+faye-websocket@^0.11.3:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
+ integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+finalhandler@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
+ integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.3"
+ statuses "~1.5.0"
+ unpipe "~1.0.0"
+
+finalhandler@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
+ integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
+ 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"
+
+find-up@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+find-up@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+ integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+ dependencies:
+ locate-path "^5.0.0"
+ path-exists "^4.0.0"
+
+flat@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
+ integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+
+flatted@^3.2.5:
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
+ integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
+
+follow-redirects@^1.0.0:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
+ integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
+
+format-util@1.0.5, format-util@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271"
+ integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==
+
+forwarded@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+ integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+
+fs-extra@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-monkey@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
+ integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-intrinsic@^1.0.2:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
+ integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.1"
+
+get-stream@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+ integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
+glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+glob@7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+ integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.1.3, glob@^7.1.6, glob@^7.1.7:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ 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"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+ version "4.2.10"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
+ integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
+
+handle-thing@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
+ integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-symbols@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+he@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+hpack.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
+ integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=
+ dependencies:
+ inherits "^2.0.1"
+ obuf "^1.0.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.1.0"
+
+html-entities@^2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
+ integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==
+
+http-deceiver@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
+ integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
+
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
+http-errors@~1.6.2:
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+ integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.3"
+ setprototypeof "1.1.0"
+ statuses ">= 1.4.0 < 2"
+
+http-parser-js@>=0.5.1:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd"
+ integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==
+
+http-proxy-middleware@^2.0.3:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
+ integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
+ 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"
+
+http-proxy@^1.18.1:
+ version "1.18.1"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
+ integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
+ dependencies:
+ eventemitter3 "^4.0.0"
+ follow-redirects "^1.0.0"
+ requires-port "^1.0.0"
+
+human-signals@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
+ integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+
+iconv-lite@0.4.24:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+iconv-lite@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+icss-utils@^5.0.0, icss-utils@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
+ integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
+
+immutable@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
+ integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
+
+import-local@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
+ integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==
+ dependencies:
+ pkg-dir "^4.2.0"
+ resolve-cwd "^3.0.0"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+inherits@2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+ integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+
+interpret@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
+ integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
+
+ipaddr.js@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+ipaddr.js@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
+ integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-builtin-module@^3.1.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
+ integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==
+ dependencies:
+ builtin-modules "^3.3.0"
+
+is-core-module@^2.11.0:
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd"
+ integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==
+ dependencies:
+ has "^1.0.3"
+
+is-core-module@^2.8.1:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
+ integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
+ dependencies:
+ has "^1.0.3"
+
+is-docker@^2.0.0, is-docker@^2.1.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
+ integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+ integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-plain-obj@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
+ integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+
+is-plain-obj@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7"
+ integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
+
+is-plain-object@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
+ integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
+ dependencies:
+ isobject "^3.0.1"
+
+is-reference@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
+ integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
+ dependencies:
+ "@types/estree" "*"
+
+is-stream@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+ integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
+is-unicode-supported@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
+ integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
+
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+isbinaryfile@^4.0.8:
+ version "4.0.10"
+ resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3"
+ integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+
+isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
+ integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
+
+jest-worker@^26.2.1:
+ version "26.6.2"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
+ integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^7.0.0"
+
+jest-worker@^27.4.5:
+ version "27.5.1"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
+ integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-yaml@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+json-parse-even-better-errors@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+karma-chrome-launcher@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz#baca9cc071b1562a1db241827257bfe5cab597ea"
+ integrity sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==
+ dependencies:
+ which "^1.2.1"
+
+karma-mocha@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d"
+ integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==
+ dependencies:
+ minimist "^1.2.3"
+
+karma-sourcemap-loader@0.3.8:
+ version "0.3.8"
+ resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz#d4bae72fb7a8397328a62b75013d2df937bdcf9c"
+ integrity sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g==
+ dependencies:
+ graceful-fs "^4.1.2"
+
+karma-webpack@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840"
+ integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==
+ dependencies:
+ glob "^7.1.3"
+ minimatch "^3.0.4"
+ webpack-merge "^4.1.5"
+
+karma@6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.0.tgz#82652dfecdd853ec227b74ed718a997028a99508"
+ integrity sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==
+ dependencies:
+ "@colors/colors" "1.5.0"
+ body-parser "^1.19.0"
+ braces "^3.0.2"
+ chokidar "^3.5.1"
+ connect "^3.7.0"
+ di "^0.0.1"
+ dom-serialize "^2.2.1"
+ glob "^7.1.7"
+ graceful-fs "^4.2.6"
+ http-proxy "^1.18.1"
+ isbinaryfile "^4.0.8"
+ lodash "^4.17.21"
+ log4js "^6.4.1"
+ mime "^2.5.2"
+ minimatch "^3.0.4"
+ mkdirp "^0.5.5"
+ qjobs "^1.2.0"
+ range-parser "^1.2.1"
+ rimraf "^3.0.2"
+ socket.io "^4.4.1"
+ source-map "^0.6.1"
+ tmp "^0.2.1"
+ ua-parser-js "^0.7.30"
+ yargs "^16.1.1"
+
+kind-of@^6.0.2:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
+ integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+
+klona@^2.0.4:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
+ integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
+
+loader-runner@^4.2.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
+ integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
+
+locate-path@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+ integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+ dependencies:
+ p-locate "^4.1.0"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.sortby@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
+ integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
+
+lodash@^4.17.15, lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+log-symbols@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
+ integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
+ dependencies:
+ chalk "^4.1.0"
+ is-unicode-supported "^0.1.0"
+
+log4js@^6.4.1:
+ version "6.5.2"
+ resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.5.2.tgz#9ae371e5b3cb3a3a209c24686e5547f8670834e5"
+ integrity sha512-DXtpNtt+KDOMT7RHUDIur/WsSA3rntlUh9Zg4XCdV42wUuMmbFkl38+LZ92Z5QvQA7mD5kAVkLiBSEH/tvUB8A==
+ dependencies:
+ date-format "^4.0.10"
+ debug "^4.3.4"
+ flatted "^3.2.5"
+ rfdc "^1.3.0"
+ streamroller "^3.1.1"
+
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
+magic-string@^0.25.7:
+ version "0.25.9"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
+ integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
+ dependencies:
+ sourcemap-codec "^1.4.8"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+
+memfs@^3.4.3:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.4.tgz#e8973cd8060548916adcca58a248e7805c715e89"
+ integrity sha512-W4gHNUE++1oSJVn8Y68jPXi+mkx3fXR5ITE/Ubz6EQ3xRpCN5k2CQ4AUR8094Z7211F876TyoBACGsIveqgiGA==
+ dependencies:
+ fs-monkey "1.0.3"
+
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+ integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+
+micromatch@^4.0.2:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+ integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+ dependencies:
+ braces "^3.0.2"
+ picomatch "^2.3.1"
+
+mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+mime@^2.5.2:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
+ integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+
+mimic-fn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+minimalistic-assert@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
+ integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
+
+minimatch@5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+ integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^3.0.4, minimatch@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.3, minimist@^1.2.6:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
+ integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+
+mkdirp@^0.5.5:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
+ integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
+ dependencies:
+ minimist "^1.2.6"
+
+mocha@10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9"
+ integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==
+ dependencies:
+ "@ungap/promise-all-settled" "1.1.2"
+ ansi-colors "4.1.1"
+ browser-stdout "1.3.1"
+ chokidar "3.5.3"
+ debug "4.3.4"
+ diff "5.0.0"
+ escape-string-regexp "4.0.0"
+ find-up "5.0.0"
+ glob "7.2.0"
+ he "1.2.0"
+ js-yaml "4.1.0"
+ log-symbols "4.1.0"
+ minimatch "5.0.1"
+ ms "2.1.3"
+ nanoid "3.3.3"
+ serialize-javascript "6.0.0"
+ strip-json-comments "3.1.1"
+ supports-color "8.1.1"
+ workerpool "6.2.1"
+ yargs "16.2.0"
+ yargs-parser "20.2.4"
+ yargs-unparser "2.0.0"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+multicast-dns@^7.2.5:
+ version "7.2.5"
+ resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced"
+ integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==
+ dependencies:
+ dns-packet "^5.2.2"
+ thunky "^1.0.2"
+
+nanoid@3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+ integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
+
+nanoid@^3.3.4:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
+ integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+neo-async@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+ integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+node-fetch@2.6.7:
+ version "2.6.7"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
+ integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+node-forge@^1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
+ integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
+
+node-releases@^2.0.3:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666"
+ integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+npm-run-path@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+ integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+ dependencies:
+ path-key "^3.0.0"
+
+object-assign@^4:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+object-inspect@^1.9.0:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
+ integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
+
+obuf@^1.0.0, obuf@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
+ integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
+
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
+ integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+onetime@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+ integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+ dependencies:
+ mimic-fn "^2.1.0"
+
+open@^8.0.9:
+ version "8.4.0"
+ resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
+ integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
+ dependencies:
+ define-lazy-prop "^2.0.0"
+ is-docker "^2.1.1"
+ is-wsl "^2.2.0"
+
+p-limit@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+ integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+ dependencies:
+ p-limit "^2.2.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+p-retry@^4.5.0:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16"
+ integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==
+ dependencies:
+ "@types/retry" "0.12.0"
+ retry "^0.13.1"
+
+p-try@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
+parseurl@~1.3.2, parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+path-key@^3.0.0, path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+ integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pkg-dir@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+ integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+ dependencies:
+ find-up "^4.0.0"
+
+postcss-modules-extract-imports@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
+ integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
+
+postcss-modules-local-by-default@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
+ integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
+ dependencies:
+ icss-utils "^5.0.0"
+ postcss-selector-parser "^6.0.2"
+ postcss-value-parser "^4.1.0"
+
+postcss-modules-scope@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
+ integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
+ dependencies:
+ postcss-selector-parser "^6.0.4"
+
+postcss-modules-values@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
+ integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
+ dependencies:
+ icss-utils "^5.0.0"
+
+postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
+ version "6.0.10"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
+ integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
+ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
+
+postcss@^8.4.7:
+ version "8.4.14"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
+ integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
+ dependencies:
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+proxy-addr@~2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+ integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+ dependencies:
+ forwarded "0.2.0"
+ ipaddr.js "1.9.1"
+
+punycode@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+ integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
+qjobs@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
+ integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
+
+qs@6.10.3:
+ version "6.10.3"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
+ integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
+ dependencies:
+ side-channel "^1.0.4"
+
+qs@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
+ integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
+ dependencies:
+ side-channel "^1.0.4"
+
+randombytes@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+ integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+ dependencies:
+ safe-buffer "^5.1.0"
+
+range-parser@^1.2.1, range-parser@~1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
+ integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
+ dependencies:
+ bytes "3.1.2"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
+readable-stream@^2.0.1:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ 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"
+
+readable-stream@^3.0.6:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+ integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+rechoir@^0.7.0:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686"
+ integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==
+ dependencies:
+ resolve "^1.9.0"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+requires-port@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+ integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+
+resolve-cwd@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
+ integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
+ dependencies:
+ resolve-from "^5.0.0"
+
+resolve-from@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
+ integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+
+resolve@^1.17.0, resolve@^1.19.0:
+ version "1.22.2"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
+ integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
+ dependencies:
+ is-core-module "^2.11.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+resolve@^1.9.0:
+ version "1.22.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
+ integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
+ dependencies:
+ is-core-module "^2.8.1"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+retry@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
+ integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
+rfdc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
+ integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
+
+rimraf@^3.0.0, rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+rollup-plugin-sourcemaps@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed"
+ integrity sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==
+ dependencies:
+ "@rollup/pluginutils" "^3.0.9"
+ source-map-resolve "^0.6.0"
+
+rollup-plugin-terser@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
+ integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ jest-worker "^26.2.1"
+ serialize-javascript "^4.0.0"
+ terser "^5.0.0"
+
+rollup@^2.68.0:
+ version "2.79.1"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
+ integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+sass-loader@13.0.2:
+ version "13.0.2"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.0.2.tgz#e81a909048e06520e9f2ff25113a801065adb3fe"
+ integrity sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==
+ dependencies:
+ klona "^2.0.4"
+ neo-async "^2.6.2"
+
+sass@1.54.3:
+ version "1.54.3"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.3.tgz#37baa2652f7f1fdadb73240ee9a2b9b81fabb5c4"
+ integrity sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==
+ dependencies:
+ chokidar ">=3.0.0 <4.0.0"
+ immutable "^4.0.0"
+ source-map-js ">=0.6.2 <2.0.0"
+
+schema-utils@^3.1.0, schema-utils@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
+ integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
+ dependencies:
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
+schema-utils@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7"
+ integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==
+ dependencies:
+ "@types/json-schema" "^7.0.9"
+ ajv "^8.8.0"
+ ajv-formats "^2.1.1"
+ ajv-keywords "^5.0.0"
+
+select-hose@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
+ integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
+
+selfsigned@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61"
+ integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==
+ dependencies:
+ node-forge "^1"
+
+semver@^7.3.5:
+ version "7.3.7"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+ integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
+ dependencies:
+ lru-cache "^6.0.0"
+
+send@0.18.0:
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
+ integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
+ 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"
+
+serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
+ integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
+ dependencies:
+ randombytes "^2.1.0"
+
+serialize-javascript@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
+ integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
+ dependencies:
+ randombytes "^2.1.0"
+
+serve-index@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
+ integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=
+ 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"
+
+serve-static@1.15.0:
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
+ integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
+ dependencies:
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.18.0"
+
+setprototypeof@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+ integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
+
+setprototypeof@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+shallow-clone@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+ integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+ dependencies:
+ kind-of "^6.0.2"
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
+signal-exit@^3.0.3:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
+socket.io-adapter@~2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6"
+ integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==
+
+socket.io-parser@~4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5"
+ integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==
+ dependencies:
+ "@socket.io/component-emitter" "~3.1.0"
+ debug "~4.3.1"
+
+socket.io@^4.4.1:
+ version "4.5.3"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.3.tgz#44dffea48d7f5aa41df4a66377c386b953bc521c"
+ integrity sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==
+ dependencies:
+ accepts "~1.3.4"
+ base64id "~2.0.0"
+ debug "~4.3.2"
+ engine.io "~6.2.0"
+ socket.io-adapter "~2.4.0"
+ socket.io-parser "~4.2.0"
+
+sockjs@^0.3.24:
+ version "0.3.24"
+ resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"
+ integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==
+ dependencies:
+ faye-websocket "^0.11.3"
+ uuid "^8.3.2"
+ websocket-driver "^0.7.4"
+
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+source-map-loader@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.0.tgz#bdc6b118bc6c87ee4d8d851f2d4efcc5abdb2ef5"
+ integrity sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==
+ dependencies:
+ abab "^2.0.6"
+ iconv-lite "^0.6.3"
+ source-map-js "^1.0.2"
+
+source-map-resolve@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2"
+ integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==
+ dependencies:
+ atob "^2.1.2"
+ decode-uri-component "^0.2.0"
+
+source-map-support@0.5.21, source-map-support@~0.5.20:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.6.0, source-map@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+source-map@~0.8.0-beta.0:
+ version "0.8.0-beta.0"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11"
+ integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==
+ dependencies:
+ whatwg-url "^7.0.0"
+
+sourcemap-codec@^1.4.8:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+ integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
+spdy-transport@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
+ integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==
+ 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"
+
+spdy@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b"
+ integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==
+ 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"
+
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+"statuses@>= 1.4.0 < 2", statuses@~1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+ integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
+
+streamroller@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.1.tgz#679aae10a4703acdf2740755307df0a05ad752e6"
+ integrity sha512-iPhtd9unZ6zKdWgMeYGfSBuqCngyJy1B/GPi/lTpwGpa3bajuX30GjUVd0/Tn/Xhg0mr4DOSENozz9Y06qyonQ==
+ dependencies:
+ date-format "^4.0.10"
+ debug "^4.3.4"
+ fs-extra "^10.1.0"
+
+string-width@^4.1.0, string-width@^4.2.0:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-final-newline@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+ integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
+strip-json-comments@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+style-loader@3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
+ integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==
+
+supports-color@8.1.1, supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-color@^7.0.0, supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+tapable@^2.1.1, tapable@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
+terser-webpack-plugin@^5.1.3:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54"
+ integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==
+ dependencies:
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.0"
+ source-map "^0.6.1"
+ terser "^5.7.2"
+
+terser@^5.0.0:
+ version "5.18.2"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.2.tgz#ff3072a0faf21ffd38f99acc9a0ddf7b5f07b948"
+ integrity sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==
+ dependencies:
+ "@jridgewell/source-map" "^0.3.3"
+ acorn "^8.8.2"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
+
+terser@^5.7.2:
+ version "5.13.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799"
+ integrity sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==
+ dependencies:
+ acorn "^8.5.0"
+ commander "^2.20.0"
+ source-map "~0.8.0-beta.0"
+ source-map-support "~0.5.20"
+
+thunky@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
+ integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
+
+tmp@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
+ integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
+ dependencies:
+ rimraf "^3.0.0"
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+tr46@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
+ integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
+ dependencies:
+ punycode "^2.1.0"
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+tslib@^2.1.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
+ integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
+
+tslib@^2.3.1:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3"
+ integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==
+
+type-is@~1.6.18:
+ version "1.6.18"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+typescript@4.7.4:
+ version "4.7.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
+ integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
+
+typescript@^3.7.2:
+ version "3.9.10"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
+ integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
+
+ua-parser-js@^0.7.30:
+ version "0.7.31"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"
+ integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
+
+universalify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
+ integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+ integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
+
+uuid@^8.3.2:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
+vary@^1, vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+
+void-elements@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+ integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
+
+watchpack@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
+
+wbuf@^1.1.0, wbuf@^1.7.3:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
+ integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==
+ dependencies:
+ minimalistic-assert "^1.0.0"
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+webidl-conversions@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
+ integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
+
+webpack-cli@4.10.0:
+ version "4.10.0"
+ resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31"
+ integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==
+ dependencies:
+ "@discoveryjs/json-ext" "^0.5.0"
+ "@webpack-cli/configtest" "^1.2.0"
+ "@webpack-cli/info" "^1.5.0"
+ "@webpack-cli/serve" "^1.7.0"
+ colorette "^2.0.14"
+ commander "^7.0.0"
+ cross-spawn "^7.0.3"
+ fastest-levenshtein "^1.0.12"
+ import-local "^3.0.2"
+ interpret "^2.2.0"
+ rechoir "^0.7.0"
+ webpack-merge "^5.7.3"
+
+webpack-dev-middleware@^5.3.1:
+ version "5.3.3"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f"
+ integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==
+ dependencies:
+ colorette "^2.0.10"
+ memfs "^3.4.3"
+ mime-types "^2.1.31"
+ range-parser "^1.2.1"
+ schema-utils "^4.0.0"
+
+webpack-dev-server@4.9.3:
+ version "4.9.3"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9"
+ integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==
+ 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.1"
+ 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"
+ open "^8.0.9"
+ p-retry "^4.5.0"
+ rimraf "^3.0.2"
+ schema-utils "^4.0.0"
+ selfsigned "^2.0.1"
+ serve-index "^1.9.1"
+ sockjs "^0.3.24"
+ spdy "^4.0.2"
+ webpack-dev-middleware "^5.3.1"
+ ws "^8.4.2"
+
+webpack-merge@^4.1.5:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d"
+ integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==
+ dependencies:
+ lodash "^4.17.15"
+
+webpack-merge@^5.7.3:
+ version "5.8.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61"
+ integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==
+ dependencies:
+ clone-deep "^4.0.1"
+ wildcard "^2.0.0"
+
+webpack-sources@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
+ integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
+
+webpack@5.74.0:
+ version "5.74.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
+ integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^0.0.51"
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/wasm-edit" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.7.6"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.10.0"
+ es-module-lexer "^0.9.0"
+ 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.1.0"
+ tapable "^2.1.1"
+ terser-webpack-plugin "^5.1.3"
+ watchpack "^2.4.0"
+ webpack-sources "^3.2.3"
+
+websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
+ integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
+ dependencies:
+ http-parser-js ">=0.5.1"
+ safe-buffer ">=5.1.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
+ integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+whatwg-url@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
+ integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
+ dependencies:
+ lodash.sortby "^4.7.0"
+ tr46 "^1.0.1"
+ webidl-conversions "^4.0.2"
+
+which@^1.2.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+ integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+ dependencies:
+ isexe "^2.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+wildcard@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
+ integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
+
+workerpool@6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+ integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+ws@8.5.0:
+ version "8.5.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
+ integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
+
+ws@^8.4.2:
+ version "8.8.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769"
+ integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==
+
+ws@~8.2.3:
+ version "8.2.3"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
+ integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yargs-parser@20.2.4:
+ version "20.2.4"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
+ integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
+
+yargs-parser@^20.2.2:
+ version "20.2.9"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+ integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
+yargs-unparser@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
+ integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
+ dependencies:
+ camelcase "^6.0.0"
+ decamelize "^4.0.0"
+ flat "^5.0.2"
+ is-plain-obj "^2.1.0"
+
+yargs@16.2.0, yargs@^16.1.1:
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+ integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+ 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"
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/lib/lib-client/build.gradle.kts b/lib/lib-client/build.gradle.kts
new file mode 100644
index 0000000..0ef3a0a
--- /dev/null
+++ b/lib/lib-client/build.gradle.kts
@@ -0,0 +1,48 @@
+plugins {
+ id("convention.library-jvm")
+ id("convention.library-android")
+ id("convention.library-js")
+ id("convention.compose")
+}
+
+android {
+ namespace = "${rootProject.group}.${rootProject.name}.client"
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ api(projects.lib.libCore)
+ api(compose.runtime)
+ api(libs.kotlinx.coroutines.core)
+
+ api(libs.redux.threadsafe)
+ api(libs.redux.compose)
+ api(libs.redux.thunk)
+
+ implementation(libs.ktor.client.core)
+ implementation(libs.ktor.client.auth)
+ implementation(libs.ktor.client.content.negotiation)
+ implementation(libs.ktor.serialization.kotlinx.json)
+ implementation(libs.ktor.serialization.kotlinx.cbor)
+ }
+ }
+ androidMain {
+ dependencies {
+ api(libs.androidx.lifecycle.compose)
+ implementation(libs.ktor.client.cio)
+ }
+ }
+ jvmMain {
+ dependencies {
+ implementation(libs.ktor.client.cio)
+ }
+ }
+ jsMain {
+ dependencies {
+ implementation(libs.ktor.client.js)
+ }
+ }
+ }
+}
diff --git a/lib/lib-client/src/androidMain/kotlin/dev/petuska/kodex/client/config/AppEnv.android.kt b/lib/lib-client/src/androidMain/kotlin/dev/petuska/kodex/client/config/AppEnv.android.kt
new file mode 100644
index 0000000..d727051
--- /dev/null
+++ b/lib/lib-client/src/androidMain/kotlin/dev/petuska/kodex/client/config/AppEnv.android.kt
@@ -0,0 +1,11 @@
+package dev.petuska.kodex.client.config
+
+import org.reduxkotlin.BuildConfig
+
+actual suspend fun loadEnv(vararg args: String): AppEnv {
+ // TODO Resolve properly
+ return object : AppEnv {
+ override val API_URL: String = "https://localhost:8080"
+ override val DEV_MODE: Boolean = BuildConfig.DEBUG
+ }
+}
diff --git a/lib/lib-client/src/androidMain/kotlin/dev/petuska/kodex/client/util/ViewModel.android.kt b/lib/lib-client/src/androidMain/kotlin/dev/petuska/kodex/client/util/ViewModel.android.kt
new file mode 100644
index 0000000..0495227
--- /dev/null
+++ b/lib/lib-client/src/androidMain/kotlin/dev/petuska/kodex/client/util/ViewModel.android.kt
@@ -0,0 +1,5 @@
+package dev.petuska.kodex.client.util
+
+import androidx.lifecycle.ViewModel
+
+actual typealias ViewModel = ViewModel
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/AppContext.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/AppContext.kt
new file mode 100644
index 0000000..1e1c26c
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/AppContext.kt
@@ -0,0 +1,10 @@
+package dev.petuska.kodex.client
+
+import dev.petuska.kodex.client.config.AppEnv
+import dev.petuska.kodex.client.store.AppStore
+
+data class AppContext(
+ val args: List,
+ val env: AppEnv,
+ override val store: AppStore,
+) : AppStore by store
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/config/AppEnv.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/config/AppEnv.kt
new file mode 100644
index 0000000..5561842
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/config/AppEnv.kt
@@ -0,0 +1,9 @@
+package dev.petuska.kodex.client.config
+
+@Suppress("PropertyName", "VariableNaming")
+interface AppEnv {
+ val API_URL: String
+ val DEV_MODE: Boolean
+}
+
+expect suspend fun loadEnv(vararg args: String): AppEnv
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/config/di.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/config/di.kt
new file mode 100644
index 0000000..f2449d8
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/config/di.kt
@@ -0,0 +1,63 @@
+package dev.petuska.kodex.client.config
+
+import dev.petuska.kodex.client.service.LibraryService
+import dev.petuska.kodex.client.store.state.AppState
+import dev.petuska.kodex.client.util.UrlUtils
+import dev.petuska.kodex.core.config.serialisationModule
+import io.ktor.client.*
+import io.ktor.client.plugins.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.client.request.*
+import io.ktor.http.*
+import io.ktor.serialization.kotlinx.cbor.*
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.serialization.ExperimentalSerializationApi
+import org.koin.core.context.startKoin
+import org.koin.core.module.dsl.onClose
+import org.koin.core.module.dsl.singleOf
+import org.koin.core.module.dsl.withOptions
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+import org.reduxkotlin.Store
+
+fun startDI(env: AppEnv, store: Store) = startKoin {
+ modules(
+ serialisationModule,
+ services,
+ clientsModule,
+ module {
+ singleOf(::UrlUtils)
+ single { env }
+ single { store }
+ }
+ )
+}
+
+private val services = module {
+ single { LibraryService(get(), get()) }
+}
+
+private val clientsModule = module {
+ factory {
+ HttpClient {
+ defaultRequest {
+ contentType(ContentType.Application.Cbor)
+ accept(ContentType.Application.Json)
+ accept(ContentType.Application.Cbor)
+ accept(ContentType.Text.Html)
+ }
+ install(HttpRequestRetry) {
+ retryOnServerErrors(maxRetries = 3)
+ retryOnException(maxRetries = 3)
+ exponentialDelay()
+ }
+ install(ContentNegotiation) {
+ json(get(named("pretty")))
+ @OptIn(ExperimentalSerializationApi::class)
+ cbor(get())
+ }
+ }
+ } withOptions {
+ onClose { it?.close() }
+ }
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/service/LibraryService.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/service/LibraryService.kt
new file mode 100644
index 0000000..31f221c
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/service/LibraryService.kt
@@ -0,0 +1,51 @@
+package dev.petuska.kodex.client.service
+
+import dev.petuska.kodex.client.util.UrlUtils
+import dev.petuska.kodex.core.domain.Count
+import dev.petuska.kodex.core.domain.KotlinLibrary
+import dev.petuska.kodex.core.domain.PagedResponse
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.plugins.onDownload
+import io.ktor.client.request.get
+
+class LibraryService(private val client: HttpClient, private val urlUtils: UrlUtils) {
+ private fun String.toApiUrl() = with(urlUtils) { toApiUrl() }
+
+ suspend fun search(
+ page: Int,
+ size: Int,
+ search: String?,
+ targets: Set?,
+ onProgress: (suspend (current: Long, total: Long) -> Unit)? = null,
+ ): PagedResponse {
+ val pagination = "page=$page&size=$size"
+ val searchQuery = search?.let { "search=$it" } ?: ""
+ val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=") ?: ""
+
+ return client.get(
+ "/api/libraries${
+ buildQuery(
+ pagination,
+ searchQuery,
+ targetsQuery
+ )
+ }".toApiUrl()
+ ) {
+ onDownload(onProgress)
+ }.body()
+ }
+
+ suspend fun count(search: String?, targets: Set?): Count {
+ val searchQuery = search?.let { "search=$it" }
+ val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=")
+
+ return client.get("/api/libraries/count${buildQuery(searchQuery, targetsQuery)}".toApiUrl())
+ .body()
+ }
+
+ private fun buildQuery(vararg query: String?): String {
+ return query.toSet().filterNotNull().takeIf(List::isNotEmpty)
+ ?.joinToString("&", prefix = "?") ?: ""
+ }
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/AppStore.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/AppStore.kt
new file mode 100644
index 0000000..c8d90e9
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/AppStore.kt
@@ -0,0 +1,19 @@
+package dev.petuska.kodex.client.store
+
+import dev.petuska.kodex.client.config.AppEnv
+import dev.petuska.kodex.client.store.reducer.loadReducer
+import dev.petuska.kodex.client.store.state.AppState
+import org.reduxkotlin.Store
+import org.reduxkotlin.applyMiddleware
+import org.reduxkotlin.createStore
+import org.reduxkotlin.thunk.createThunkMiddleware
+
+typealias AppStore = Store
+
+fun loadStore(env: AppEnv) = createStore(
+ loadReducer(),
+ AppState(env),
+ applyMiddleware(
+ createThunkMiddleware()
+ )
+)
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/action/AppAction.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/action/AppAction.kt
new file mode 100644
index 0000000..21f2afd
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/action/AppAction.kt
@@ -0,0 +1,26 @@
+package dev.petuska.kodex.client.store.action
+
+import dev.petuska.kodex.client.store.state.Page
+import dev.petuska.kodex.core.domain.KotlinLibrary
+import dev.petuska.kodex.core.domain.KotlinTarget
+import dev.petuska.kodex.core.domain.PagedResponse
+
+sealed class AppAction {
+ object ToggleDrawer : AppAction()
+ data class SetDrawer(val isOpen: Boolean) : AppAction()
+ data class SetLibraries(val libraries: PagedResponse?) : AppAction()
+ data class SetSearch(val search: String?) : AppAction()
+ data class SetTargets(val targets: Set?) : AppAction()
+ data class AddTargets(val targets: Set) : AppAction() {
+ constructor(target: KotlinTarget) : this(setOf(target))
+ }
+
+ data class RemoveTargets(val targets: Set) : AppAction() {
+ constructor(target: KotlinTarget) : this(setOf(target))
+ }
+
+ data class SetCount(val count: Long?) : AppAction()
+ data class SetLoading(val loading: Boolean, val progress: Number? = null) : AppAction()
+ data class SetPage(val page: Page) : AppAction()
+ data class SetDarkTheme(val enabled: Boolean) : AppAction()
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/reducer/AppReducer.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/reducer/AppReducer.kt
new file mode 100644
index 0000000..f2d5fdc
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/reducer/AppReducer.kt
@@ -0,0 +1,26 @@
+package dev.petuska.kodex.client.store.reducer
+
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.store.state.AppState
+import org.reduxkotlin.typedReducer
+
+fun loadReducer() = typedReducer { state, action ->
+ when (action) {
+ is AppAction.SetLibraries -> state.copy(libraries = action.libraries)
+ is AppAction.SetSearch -> state.copy(search = action.search)
+ is AppAction.SetTargets -> state.copy(targets = action.targets)
+ is AppAction.SetCount -> state.copy(count = action.count)
+
+ is AppAction.ToggleDrawer -> state.copy(drawerOpen = !state.drawerOpen)
+ is AppAction.SetDrawer -> state.copy(drawerOpen = action.isOpen)
+ is AppAction.SetLoading -> state.copy(
+ loading = action.loading,
+ progress = action.progress.takeIf { action.loading },
+ )
+
+ is AppAction.AddTargets -> state.copy(targets = action.targets + (state.targets ?: setOf()))
+ is AppAction.RemoveTargets -> state.copy(targets = (state.targets ?: setOf()) - action.targets)
+ is AppAction.SetPage -> state.copy(page = action.page)
+ is AppAction.SetDarkTheme -> state.copy(darkTheme = action.enabled)
+ }
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/state/AppState.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/state/AppState.kt
new file mode 100644
index 0000000..177cb71
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/state/AppState.kt
@@ -0,0 +1,28 @@
+package dev.petuska.kodex.client.store.state
+
+import dev.petuska.kodex.client.config.AppEnv
+import dev.petuska.kodex.core.domain.KotlinLibrary
+import dev.petuska.kodex.core.domain.KotlinTarget
+import dev.petuska.kodex.core.domain.PagedResponse
+
+data class AppState(
+ val env: AppEnv,
+ val page: Page = Page.Home,
+ val count: Long? = null,
+ val libraries: PagedResponse? = null,
+ val search: String? = null,
+ val targets: Set? = null,
+ val drawerOpen: Boolean = false,
+ val progress: Number? = null,
+ val loading: Boolean = false,
+ val darkTheme: Boolean = true,
+)
+
+enum class Page(val icon: String) {
+ Home("home"), Search("search"), Statistics("stacked_bar_chart"), Random("casino");
+
+ val route: String = name.lowercase()
+ override fun toString(): String {
+ return route
+ }
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/thunk/AppThunk.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/thunk/AppThunk.kt
new file mode 100644
index 0000000..49c0493
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/store/thunk/AppThunk.kt
@@ -0,0 +1,77 @@
+package dev.petuska.kodex.client.store.thunk
+
+import dev.petuska.kodex.client.service.LibraryService
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.store.state.AppState
+import dev.petuska.kodex.client.util.suspending
+import dev.petuska.kodex.core.domain.KotlinTarget
+import org.reduxkotlin.thunk.Thunk
+
+typealias AppThunk = Thunk
+
+fun LibraryService.fetchLibraryPage(
+ page: Int,
+ size: Int = 12,
+ search: String? = null,
+ targets: Set? = null,
+): AppThunk = { dispatch, getState, _ ->
+ suspending {
+ val state = getState()
+ val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty)
+ val theTargets = (targets ?: state.targets)?.takeIf(Set<*>::isNotEmpty)
+
+ dispatch(AppAction.SetLoading(true))
+ val theLibraries =
+ search(
+ page,
+ size,
+ theSearch,
+ theTargets?.map(KotlinTarget::platform)?.toSet()
+ ) { current, total ->
+ val progress = total.toDouble() / current
+ dispatch(AppAction.SetLoading(progress > 0, progress))
+ }
+ dispatch(AppAction.SetSearch(theSearch))
+ dispatch(AppAction.SetTargets(theTargets))
+ dispatch(AppAction.SetLibraries(theLibraries))
+ dispatch(AppAction.SetLoading(false))
+ }
+}
+
+fun parseQuery(query: String): AppThunk = { dispatch, _, _ ->
+ val parameters = query.split("&")
+ .map { it.split("=").let { (k, v) -> k to v } }
+ .groupBy { it.first }.mapValues { (_, v) -> v.map { it.second }.toSet() }
+
+ parameters["target"]?.let { targets ->
+ val kotlinTargets = KotlinTarget.values().let {
+ targets.mapNotNull { target ->
+ it.find { t -> target == t.platform }
+ }
+ }.toSet()
+ dispatch(AppAction.SetTargets(kotlinTargets))
+ }
+
+ parameters["search"].let { search ->
+ dispatch(AppAction.SetSearch(search?.joinToString("")))
+ }
+
+ Unit
+}
+
+fun LibraryService.fetchLibraryCount(
+ search: String? = null,
+ targets: Set? = null,
+): AppThunk = { dispatch, getState, _ ->
+ suspending {
+ val state = getState()
+ val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty)
+ val theTargets = (targets ?: state.targets)?.takeIf(Set<*>::isNotEmpty)
+
+ val theCount = count(theSearch, theTargets?.map(KotlinTarget::toString)?.toSet()).count
+
+ dispatch(AppAction.SetCount(theCount))
+ dispatch(AppAction.SetSearch(theSearch))
+ dispatch(AppAction.SetTargets(theTargets))
+ }
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/UrlUtils.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/UrlUtils.kt
new file mode 100644
index 0000000..46b86fb
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/UrlUtils.kt
@@ -0,0 +1,19 @@
+package dev.petuska.kodex.client.util
+
+import dev.petuska.kodex.client.config.AppEnv
+
+class UrlUtils(private val env: AppEnv) {
+ fun String.toApiUrl() = "${env.API_URL}/${removePrefix("/")}"
+}
+
+private val urlParameterRegex = Regex("""\{(.+)\}""")
+fun buildUrl(url: String, vararg parameters: Pair): String {
+ val requiredParameters = urlParameterRegex.findAll(url)
+ .map { it.groups[1]?.value ?: error("${it.value} doesn't conform to url param schema") }
+
+ val paramMap = parameters.toMap()
+ return (sequenceOf(url) + requiredParameters).reduce { acc, parameter ->
+ val value = paramMap[parameter] ?: error("Value for parameter $parameter not supplied")
+ acc.replace("{$parameter}", value.toString())
+ }
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/ViewModel.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/ViewModel.kt
new file mode 100644
index 0000000..a648775
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/ViewModel.kt
@@ -0,0 +1,3 @@
+package dev.petuska.kodex.client.util
+
+expect abstract class ViewModel
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/coroutines.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/coroutines.kt
new file mode 100644
index 0000000..8f0dd5d
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/coroutines.kt
@@ -0,0 +1,19 @@
+package dev.petuska.kodex.client.util
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlin.coroutines.EmptyCoroutineContext
+
+inline fun suspending(
+ crossinline block: suspend CoroutineScope.(T1, T2) -> Unit
+): (T1, T2) -> Unit = { t1, t2 -> suspending { block(t1, t2) } }
+
+inline fun suspending(
+ crossinline block: suspend CoroutineScope.(T1) -> Unit
+): (T1) -> Unit = { suspending { block(it) } }
+
+inline fun suspending(
+ crossinline block: suspend CoroutineScope.() -> Unit
+) {
+ CoroutineScope(EmptyCoroutineContext).launch { block() }
+}
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/redux-compose.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/redux-compose.kt
new file mode 100644
index 0000000..d17f4d4
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/util/redux-compose.kt
@@ -0,0 +1,11 @@
+package dev.petuska.kodex.client.util
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisallowComposableCalls
+import dev.petuska.kodex.client.store.state.AppState
+import org.reduxkotlin.compose.selectState
+
+@Composable
+inline fun select(
+ crossinline selector: @DisallowComposableCalls AppState.() -> TSlice
+) = selectState(selector)
diff --git a/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/view/KodexApp.kt b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/view/KodexApp.kt
new file mode 100644
index 0000000..50a16a8
--- /dev/null
+++ b/lib/lib-client/src/commonMain/kotlin/dev/petuska/kodex/client/view/KodexApp.kt
@@ -0,0 +1,15 @@
+package dev.petuska.kodex.client.view
+
+import androidx.compose.runtime.Composable
+import dev.petuska.kodex.client.config.AppEnv
+import dev.petuska.kodex.client.config.startDI
+import dev.petuska.kodex.client.store.AppStore
+import dev.petuska.kodex.client.store.loadStore
+import org.reduxkotlin.compose.StoreProvider
+
+@Composable
+fun KodexApp(env: AppEnv, content: @Composable AppStore.() -> Unit) {
+ val store = loadStore(env)
+ startDI(env, store)
+ StoreProvider(store, content = content)
+}
diff --git a/lib/lib-client/src/jsMain/kotlin/dev/petuska/kodex/client/config/AppEnv.js.kt b/lib/lib-client/src/jsMain/kotlin/dev/petuska/kodex/client/config/AppEnv.js.kt
new file mode 100644
index 0000000..4c552cc
--- /dev/null
+++ b/lib/lib-client/src/jsMain/kotlin/dev/petuska/kodex/client/config/AppEnv.js.kt
@@ -0,0 +1,24 @@
+package dev.petuska.kodex.client.config
+
+import kotlinx.browser.window
+import kotlinx.coroutines.await
+import kotlin.js.Json
+
+actual suspend fun loadEnv(vararg args: String): AppEnv {
+ val envJson: Json = window.fetch("/application.env").await().text().await()
+ .split("\n")
+ .filter(String::isNotBlank)
+ .joinToString(",", "{", "}") {
+ val (key, value) = it.split("=", limit = 2).let { c ->
+ c[0] to c[1]
+ }
+ "\"$key\": \"$value\""
+ }.let(JSON::parse)
+ val env = object : AppEnv {
+ override val API_URL: String = envJson["API_URL"]!!.toString()
+ override val DEV_MODE: Boolean =
+ envJson["DEV_MODE"]?.let { !"$it".equals("false", ignoreCase = true) } ?: false
+ }
+ window.asDynamic().env = env
+ return env
+}
diff --git a/lib/lib-client/src/jsMain/kotlin/dev/petuska/kodex/client/util/ViewModel.js.kt b/lib/lib-client/src/jsMain/kotlin/dev/petuska/kodex/client/util/ViewModel.js.kt
new file mode 100644
index 0000000..ed6eccb
--- /dev/null
+++ b/lib/lib-client/src/jsMain/kotlin/dev/petuska/kodex/client/util/ViewModel.js.kt
@@ -0,0 +1,3 @@
+package dev.petuska.kodex.client.util
+
+actual abstract class ViewModel
diff --git a/lib/lib-client/src/jvmMain/kotlin/dev/petuska/kodex/client/config/AppEnv.jvm.kt b/lib/lib-client/src/jvmMain/kotlin/dev/petuska/kodex/client/config/AppEnv.jvm.kt
new file mode 100644
index 0000000..64d6e48
--- /dev/null
+++ b/lib/lib-client/src/jvmMain/kotlin/dev/petuska/kodex/client/config/AppEnv.jvm.kt
@@ -0,0 +1,9 @@
+package dev.petuska.kodex.client.config
+
+actual suspend fun loadEnv(vararg args: String): AppEnv {
+ // TODO Resolve properly
+ return object : AppEnv {
+ override val API_URL: String = "https://localhost:8080"
+ override val DEV_MODE: Boolean = true
+ }
+}
diff --git a/lib/lib-client/src/jvmMain/kotlin/dev/petuska/kodex/client/util/ViewModel.jvm.kt b/lib/lib-client/src/jvmMain/kotlin/dev/petuska/kodex/client/util/ViewModel.jvm.kt
new file mode 100644
index 0000000..ed6eccb
--- /dev/null
+++ b/lib/lib-client/src/jvmMain/kotlin/dev/petuska/kodex/client/util/ViewModel.jvm.kt
@@ -0,0 +1,3 @@
+package dev.petuska.kodex.client.util
+
+actual abstract class ViewModel
diff --git a/lib/lib-core/build.gradle.kts b/lib/lib-core/build.gradle.kts
new file mode 100644
index 0000000..5372a0a
--- /dev/null
+++ b/lib/lib-core/build.gradle.kts
@@ -0,0 +1,32 @@
+plugins {
+ id("convention.library-js")
+ id("convention.library-jvm")
+ id("convention.library-android")
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.ksp)
+}
+
+android {
+ namespace = "${rootProject.group}.${rootProject.name}.core"
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ api(libs.koin.core)
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.kotlinx.serialization.cbor)
+ }
+ }
+ commonTest {
+ dependencies {
+ implementation(projects.lib.libTest)
+ }
+ }
+ }
+}
+
+dependencies {
+ kspCommonMainMetadata(libs.koin.compiler)
+}
diff --git a/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/config/serialisation.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/config/serialisation.kt
new file mode 100644
index 0000000..f52e84b
--- /dev/null
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/config/serialisation.kt
@@ -0,0 +1,19 @@
+package dev.petuska.kodex.core.config
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.cbor.Cbor
+import kotlinx.serialization.json.Json
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+
+val serialisationModule = module {
+ single(named("pretty")) {
+ Json {
+ prettyPrint = true
+ ignoreUnknownKeys = true
+ }
+ }
+ single { Json { ignoreUnknownKeys = true } }
+ @OptIn(ExperimentalSerializationApi::class)
+ single { Cbor { ignoreUnknownKeys = true } }
+}
diff --git a/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/Count.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/Count.kt
new file mode 100644
index 0000000..914814b
--- /dev/null
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/Count.kt
@@ -0,0 +1,8 @@
+package dev.petuska.kodex.core.domain
+
+import kotlinx.serialization.Serializable
+import kotlin.jvm.JvmInline
+
+@Serializable
+@JvmInline
+value class Count(val count: Long)
diff --git a/common/src/commonMain/kotlin/kamp/domain/KotlinMPPLibrary.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/KotlinLibrary.kt
similarity index 61%
rename from common/src/commonMain/kotlin/kamp/domain/KotlinMPPLibrary.kt
rename to lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/KotlinLibrary.kt
index 0eb8f78..3484025 100644
--- a/common/src/commonMain/kotlin/kamp/domain/KotlinMPPLibrary.kt
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/KotlinLibrary.kt
@@ -1,10 +1,10 @@
-package kamp.domain
+package dev.petuska.kodex.core.domain
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
-public data class KotlinMPPLibrary(
+data class KotlinLibrary(
override val group: String,
override val name: String,
override val latestVersion: String,
@@ -15,32 +15,29 @@ public data class KotlinMPPLibrary(
val description: String?,
val website: String?,
val scm: String?,
-) : MavenArtifact {
- public constructor(
- artifact: MavenArtifact,
+) : MavenArtefact {
+ constructor(
+ artefact: MavenArtefact,
targets: Set,
description: String?,
website: String?,
scm: String?,
) : this(
- group = artifact.group,
- name = artifact.name,
- latestVersion = artifact.latestVersion,
- releaseVersion = artifact.releaseVersion,
- versions = artifact.versions,
- lastUpdated = artifact.lastUpdated,
+ group = artefact.group,
+ name = artefact.name,
+ latestVersion = artefact.latestVersion,
+ releaseVersion = artefact.releaseVersion,
+ versions = artefact.versions,
+ lastUpdated = artefact.lastUpdated,
targets = targets,
description = description,
website = website,
scm = scm
)
- val _id: String
+ @Suppress("PropertyName", "unused", "VariableNaming")
+ val _id: String = "$group:$name"
@Transient
val isMultiplatform: Boolean = targets.size > 1
-
- init {
- _id = "$group:$name"
- }
}
diff --git a/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/KotlinTarget.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/KotlinTarget.kt
new file mode 100644
index 0000000..5d700cd
--- /dev/null
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/KotlinTarget.kt
@@ -0,0 +1,280 @@
+package dev.petuska.kodex.core.domain
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
+import kotlinx.serialization.descriptors.element
+import kotlinx.serialization.encoding.CompositeDecoder
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.encoding.decodeStructure
+import kotlinx.serialization.encoding.encodeStructure
+
+@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
+@Serializable(with = KotlinTarget.Serializer::class)
+sealed class KotlinTarget(
+ val category: String,
+ val family: String = category,
+ val platform: String,
+ val displayCategory: String = category,
+ val displayPlatform: String = platform,
+ val id: String = platform,
+) {
+ object Serializer : KSerializer {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("KotlinTarget") {
+ element("id")
+ element("category")
+ element("family")
+ element("platform")
+ }
+
+ override fun deserialize(decoder: Decoder): KotlinTarget {
+ return decoder.decodeStructure(descriptor) {
+ var category: String? = null
+ var platform: String? = null
+ var id: String? = null
+ while (true) {
+ when (val index = decodeElementIndex(descriptor)) {
+ 0 -> id = decodeStringElement(descriptor, 0)
+ 1 -> category = decodeStringElement(descriptor, 1)
+ 2 -> decodeStringElement(descriptor, 2)
+ 3 -> platform = decodeStringElement(descriptor, 3)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+ requireNotNull(id)
+ requireNotNull(category)
+ requireNotNull(platform)
+ fromString(id)
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: KotlinTarget) {
+ encoder.encodeStructure(descriptor) {
+ encodeStringElement(descriptor, 0, value.id)
+ encodeStringElement(descriptor, 1, value.category)
+ encodeStringElement(descriptor, 2, value.family)
+ encodeStringElement(descriptor, 3, value.platform)
+ }
+ }
+ }
+
+ override fun toString(): String = "$category:$platform"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is KotlinTarget) return false
+
+ if (id != other.id) return false
+ if (platform != other.platform) return false
+ if (family != other.family) return false
+ if (category != other.category) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = category.hashCode()
+ result = 31 * result + family.hashCode()
+ result = 31 * result + platform.hashCode()
+ result = 31 * result + id.hashCode()
+ return result
+ }
+
+ companion object {
+ fun values(): Set =
+ setOf(Common, Wasm) + JVM.values() + JS.values() + Native.values()
+
+ fun fromString(id: String): KotlinTarget {
+ return values().find { it.id == id } ?: Unknown(id)
+ }
+ }
+
+ // ===============================================================================================
+
+ class Unknown(platform: String) : KotlinTarget(
+ category = category,
+ platform = platform,
+ displayCategory = "Unknown",
+ ) {
+ companion object {
+ const val category = "unknown"
+ }
+ }
+
+ object Common :
+ KotlinTarget(category = "metadata", platform = "common", displayCategory = "Metadata")
+
+ object Wasm : KotlinTarget(category = "wasm", platform = "wasm", displayCategory = "Wasm")
+
+ sealed class JVM(platform: String) : KotlinTarget(
+ category = category,
+ platform = platform,
+ displayCategory = "JVM",
+ id = "${category}_$platform"
+ ) {
+ companion object {
+ const val category = "jvm"
+ fun values(): Set = setOf(Java, Android)
+ }
+
+ object Java : JVM("java")
+ object Android : JVM("android")
+ }
+
+ sealed class JS(platform: String) : KotlinTarget(
+ category = category,
+ platform = platform,
+ displayCategory = "JS",
+ id = "${category}_$platform"
+ ) {
+ companion object {
+ const val category = "js"
+ fun values(): Set = setOf(Legacy, IR)
+ }
+
+ object Legacy : JS("legacy")
+ object IR : JS("ir")
+ }
+
+ sealed class Native(
+ family: String,
+ platform: String,
+ id: String = platform
+ ) : KotlinTarget(
+ category = category,
+ platform = platform,
+ family = family,
+ displayCategory = "Native",
+ id = id
+ ) {
+ companion object {
+ const val category = "native"
+ fun values(): Set =
+ AndroidNative.values() +
+ IOS.values() +
+ WatchOS.values() +
+ TvOS.values() +
+ MacOS.values() +
+ Mingw.values() +
+ Linux.values() +
+ Wasm32
+ }
+
+ object Wasm32 : Native("wasm", "wasm32")
+
+ sealed class AndroidNative(
+ val architecture: String,
+ id: String
+ ) : Native(FAMILY, "$FAMILY$architecture", "${architectureId}_$id") {
+ companion object {
+ const val FAMILY = "androidNative"
+ private const val architectureId = "android"
+ fun values(): Set =
+ setOf(
+ AndroidNativeArm32,
+ AndroidNativeArm64,
+ AndroidNativeX86,
+ AndroidNativeX64
+ )
+ }
+
+ object AndroidNativeArm32 : AndroidNative("Arm32", "arm32")
+ object AndroidNativeArm64 : AndroidNative("Arm64", "arm64")
+ object AndroidNativeX86 : AndroidNative("X86", "x86")
+ object AndroidNativeX64 : AndroidNative("X64", "x64")
+ }
+
+ sealed class IOS(
+ val architecture: String,
+ id: String
+ ) : Native(FAMILY, "$FAMILY$architecture", "${FAMILY}_$id") {
+ companion object {
+ const val FAMILY = "ios"
+ fun values(): Set = setOf(IOSArm32, IOSArm64, IOSX64, IOSSimulatorArm64)
+ }
+
+ object IOSArm32 : IOS("Arm32", "arm32")
+ object IOSArm64 : IOS("Arm64", "arm64")
+ object IOSX64 : IOS("X64", "x64")
+ object IOSSimulatorArm64 : IOS("SimulatorArm64", "simulator_arm64")
+ }
+
+ sealed class WatchOS(
+ val architecture: String,
+ id: String
+ ) : Native(FAMILY, "$FAMILY$architecture", "${FAMILY}_$id") {
+ companion object {
+ const val FAMILY = "watchos"
+ fun values(): Set =
+ setOf(WatchOSX86, WatchOSX64, WatchOSArm64, WatchOSArm32, WatchOSSimulatorArm64)
+ }
+
+ object WatchOSX86 : WatchOS("X86", "x86")
+ object WatchOSX64 : WatchOS("X64", "x64")
+ object WatchOSArm64 : WatchOS("Arm64", "arm64")
+ object WatchOSArm32 : WatchOS("Arm32", "arm32")
+ object WatchOSSimulatorArm64 : WatchOS("SimulatorArm64", "simulator_arm64")
+ }
+
+ sealed class TvOS(
+ val architecture: String,
+ id: String
+ ) : Native(FAMILY, "$FAMILY$architecture", "${FAMILY}_$id") {
+ companion object {
+ const val FAMILY = "tvos"
+ fun values(): Set = setOf(TvOSArm64, TvOSX64, TvOSSimulatorArm64)
+ }
+
+ object TvOSArm64 : TvOS("Arm64", "arm64")
+ object TvOSX64 : TvOS("X64", "x64")
+ object TvOSSimulatorArm64 : TvOS("SimulatorArm64", "simulator_arm64")
+ }
+
+ sealed class MacOS(
+ val architecture: String,
+ id: String
+ ) : Native(FAMILY, "$FAMILY$architecture", "${FAMILY}_$id") {
+ companion object {
+ const val FAMILY = "macos"
+ fun values(): Set = setOf(MacOSArm64, MacOSX64)
+ }
+
+ object MacOSArm64 : MacOS("Arm64", "arm64")
+ object MacOSX64 : MacOS("X64", "x64")
+ }
+
+ sealed class Mingw(
+ val architecture: String,
+ id: String
+ ) : Native(FAMILY, "$FAMILY$architecture", "${FAMILY}_$id") {
+ companion object {
+ const val FAMILY = "mingw"
+ fun values(): Set = setOf(MingwX64, MingwX86)
+ }
+
+ object MingwX64 : Mingw("X64", "x64")
+
+ object MingwX86 : Mingw("X86", "x86")
+ }
+
+ sealed class Linux(
+ val architecture: String,
+ id: String
+ ) : Native(FAMILY, "$FAMILY$architecture", "${FAMILY}_$id") {
+ companion object {
+ const val FAMILY = "linux"
+ fun values(): Set =
+ setOf(LinuxArm32Hfp, LinuxMips32, LinuxMipsel32, LinuxX64, LinuxArm64)
+ }
+
+ object LinuxArm32Hfp : Linux("Arm32Hfp", "arm32_hfp")
+ object LinuxMips32 : Linux("Mips32", "mips32")
+ object LinuxMipsel32 : Linux("Mipsel32", "mipsel32")
+ object LinuxX64 : Linux("X64", "x64")
+ object LinuxArm64 : Linux("Arm64", "arm64")
+ }
+ }
+}
diff --git a/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/LibrariesStatistic.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/LibrariesStatistic.kt
new file mode 100644
index 0000000..cb355de
--- /dev/null
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/LibrariesStatistic.kt
@@ -0,0 +1,16 @@
+package dev.petuska.kodex.core.domain
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class LibrariesStatistic(
+ val date: String,
+ val ts: Long,
+ val countTotal: Int,
+ val countByCategory: Map,
+ val countByFamily: Map,
+ val countByPlatform: Map,
+) {
+ @Suppress("PropertyName", "unused", "VariableNaming")
+ val _id: String = date
+}
diff --git a/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/MavenArtefact.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/MavenArtefact.kt
new file mode 100644
index 0000000..bc0fe08
--- /dev/null
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/MavenArtefact.kt
@@ -0,0 +1,22 @@
+package dev.petuska.kodex.core.domain
+
+import kotlinx.serialization.Transient
+
+interface MavenArtefact {
+ val group: String
+ val name: String
+ val latestVersion: String
+ val releaseVersion: String?
+ val versions: List?
+ val lastUpdated: Long?
+
+ @Transient
+ val version: String
+ get() = releaseVersion ?: latestVersion
+
+ @Transient
+ val id: String get() = "$group:$name"
+
+ @Transient
+ val path: String get() = "$id:$version"
+}
diff --git a/app/src/commonMain/kotlin/app/domain/PagedResponse.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/PagedResponse.kt
similarity index 81%
rename from app/src/commonMain/kotlin/app/domain/PagedResponse.kt
rename to lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/PagedResponse.kt
index a748054..bfbad33 100644
--- a/app/src/commonMain/kotlin/app/domain/PagedResponse.kt
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/domain/PagedResponse.kt
@@ -1,11 +1,11 @@
-package app.domain
+package dev.petuska.kodex.core.domain
import kotlinx.serialization.Serializable
@Serializable
data class PagedResponse(
val data: List,
+ val prev: String?,
val page: Int,
val next: String?,
- val prev: String?,
)
diff --git a/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/util/builders.kt b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/util/builders.kt
new file mode 100644
index 0000000..2a646d7
--- /dev/null
+++ b/lib/lib-core/src/commonMain/kotlin/dev/petuska/kodex/core/util/builders.kt
@@ -0,0 +1,3 @@
+package dev.petuska.kodex.core.util
+
+inline fun buildIf(condition: Boolean, action: () -> T): T? = if (condition) action() else null
diff --git a/lib/lib-core/src/commonTest/kotlin/dev/petuska/kodex/core/domain/KotlinTargetTest.kt b/lib/lib-core/src/commonTest/kotlin/dev/petuska/kodex/core/domain/KotlinTargetTest.kt
new file mode 100644
index 0000000..329e011
--- /dev/null
+++ b/lib/lib-core/src/commonTest/kotlin/dev/petuska/kodex/core/domain/KotlinTargetTest.kt
@@ -0,0 +1,23 @@
+package dev.petuska.kodex.core.domain
+
+import dev.petuska.kodex.test.dynamicTests
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldBeEqualIgnoringCase
+import kotlinx.serialization.json.Json
+import kotlin.test.Test
+
+class KotlinTargetTest {
+ @Test
+ fun tests() = dynamicTests {
+ (KotlinTarget.values() + KotlinTarget.Unknown("test")).forEach { target ->
+ "KotlinTarget ${target::class.simpleName} should be able to handle serialization" {
+ val json = Json.encodeToString(KotlinTarget.serializer(), target)
+ json shouldBeEqualIgnoringCase """{"id":"${target.id}",""" +
+ """"category":"${target.category}",""" +
+ """"family":"${target.family}",""" +
+ """"platform":"${target.platform}"}"""
+ Json.decodeFromString(KotlinTarget.serializer(), json) shouldBe target
+ }
+ }
+ }
+}
diff --git a/lib/lib-core/src/jvmMain/kotlin/dev/petuska/kodex/core/util/Env.kt b/lib/lib-core/src/jvmMain/kotlin/dev/petuska/kodex/core/util/Env.kt
new file mode 100644
index 0000000..8f37872
--- /dev/null
+++ b/lib/lib-core/src/jvmMain/kotlin/dev/petuska/kodex/core/util/Env.kt
@@ -0,0 +1,33 @@
+package dev.petuska.kodex.core.util
+
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+abstract class Env {
+ private val variables = mutableMapOf()
+
+ protected inner class EnvDelegate(
+ private val converter: EnvDelegate<*>.(String?) -> T
+ ) : ReadOnlyProperty {
+ private inline val env get() = System.getenv()
+ private val camelSplitRegex = "(?): T {
+ return value ?: converter(
+ env[property.name]
+ ?: env[property.name.uppercase().replace("-", "_")]
+ ?: env[property.name.split(camelSplitRegex).joinToString("_") { it.uppercase() }]
+ ).also {
+ variables[property.name] = it
+ value = it
+ }
+ }
+
+ fun findEnv(name: String): String? = env[name]
+ }
+
+ override fun toString(): String {
+ return variables.entries.joinToString("\n") { (key, value) -> "$key=$value" }
+ }
+}
diff --git a/lib/lib-core/src/jvmMain/resources/logback.xml b/lib/lib-core/src/jvmMain/resources/logback.xml
new file mode 100644
index 0000000..a371437
--- /dev/null
+++ b/lib/lib-core/src/jvmMain/resources/logback.xml
@@ -0,0 +1,45 @@
+
+
+
+
+ System.err
+
+ WARN
+
+
+ [%-5.5level][%d{HH:mm:ss}][%-25.25logger{25}] %msg%n
+
+
+
+
+ System.out
+
+ DEBUG
+ ACCEPT
+
+
+ INFO
+ ACCEPT
+
+
+ TRACE
+ ACCEPT
+
+
+ WARN
+ DENY
+
+
+ ERROR
+ DENY
+
+
+ [%-5.5level][%d{HH:mm:ss}][%-25.25logger{25}] %msg%n
+
+
+
+
+
+
+
+
diff --git a/lib/lib-repository/build.gradle.kts b/lib/lib-repository/build.gradle.kts
new file mode 100644
index 0000000..14df943
--- /dev/null
+++ b/lib/lib-repository/build.gradle.kts
@@ -0,0 +1,19 @@
+plugins {
+ id("convention.library-jvm")
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ implementation(projects.lib.libCore)
+ implementation(libs.koin.logger.slf4j)
+ }
+ }
+ jvmMain {
+ dependencies {
+ implementation(libs.kmongo)
+ }
+ }
+ }
+}
diff --git a/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/LibraryRepository.kt b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/LibraryRepository.kt
new file mode 100644
index 0000000..c36aaf4
--- /dev/null
+++ b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/LibraryRepository.kt
@@ -0,0 +1,157 @@
+package dev.petuska.kodex.repository
+
+import com.mongodb.client.model.Aggregates
+import com.mongodb.client.model.Facet
+import com.mongodb.client.model.Field
+import dev.petuska.kodex.core.domain.KotlinLibrary
+import dev.petuska.kodex.core.domain.KotlinTarget
+import dev.petuska.kodex.core.domain.LibrariesStatistic
+import dev.petuska.kodex.repository.util.runCatchingIO
+import org.litote.kmongo.MongoOperator.*
+import org.litote.kmongo.addFields
+import org.litote.kmongo.bson
+import org.litote.kmongo.coroutine.CoroutineCollection
+import org.litote.kmongo.coroutine.aggregate
+import org.litote.kmongo.facet
+import java.awt.SystemColor.text
+import java.text.SimpleDateFormat
+import java.util.*
+
+class LibraryRepository(private val collection: CoroutineCollection) {
+ private fun buildQuery(querySearch: String?, targets: Set?): Pair {
+ val searchQuery = querySearch?.let {
+ """
+ {
+ $text: {
+ $search: '${querySearch.replace("'", "\\'")}',
+ $language: 'en'
+ }
+ }
+ """.trimIndent()
+ }
+ val targetsQuery = targets?.let {
+ """
+ {
+ 'targets.platform': {
+ $all: [${targets.joinToString(",") { "'$it'" }}]
+ }
+ }
+ """.trimIndent()
+ }
+ val finalQuery = setOfNotNull(searchQuery, targetsQuery).takeIf { it.isNotEmpty() }?.let {
+ """
+ {
+ $and: [${it.joinToString(",")}]
+ }
+ """.trimIndent()
+ }
+
+ val projection = querySearch?.let {
+ """
+ { score: { $meta: "textScore" } }
+ """.trimIndent()
+ }
+
+ return finalQuery to projection
+ }
+
+ suspend fun search(
+ page: Int,
+ size: Int,
+ search: String?,
+ targets: Set?,
+ ): List {
+ val (query, projection) = buildQuery(search, targets)
+
+ return runCatchingIO {
+ val dbCall = query?.let(collection::find) ?: collection.find()
+ projection?.let {
+ dbCall.projection(it.bson)
+ dbCall.sort("""{ score: { $meta: "textScore" } }""".bson)
+ } ?: dbCall.ascendingSort(KotlinLibrary::name)
+ dbCall.skip(size * (page - 1)).limit(size)
+ dbCall.toList()
+ }.getOrThrow()
+ }
+
+ suspend fun count(search: String?, targets: Set?): Long {
+ return runCatchingIO {
+ collection.countDocuments(
+ buildQuery(search, targets).first ?: "{}"
+ )
+ }.getOrThrow()
+ }
+
+ suspend fun create(library: KotlinLibrary) {
+ runCatchingIO { collection.save(library) }.getOrThrow()
+ }
+
+ suspend fun findById(id: String): KotlinLibrary? {
+ return runCatchingIO { collection.findOneById(id) }.getOrThrow()
+ }
+
+ @Suppress("LongMethod")
+ suspend fun captureStatistics(): LibrariesStatistic {
+ val categories = listOf(
+ KotlinTarget.Common.category,
+ KotlinTarget.JVM.category,
+ KotlinTarget.JS.category,
+ KotlinTarget.Wasm.category,
+ KotlinTarget.Native.category,
+ KotlinTarget.Unknown.category,
+ )
+ val families = KotlinTarget.values().map(KotlinTarget::family).toSet() +
+ KotlinTarget.Unknown("unknown").family
+ val platforms = KotlinTarget.values().map(KotlinTarget::id)
+ val date = Date()
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.UK)
+
+ val facets = buildList {
+ add(Facet("total", Aggregates.count()))
+ categories.map {
+ Facet("c$it", Aggregates.match("{ 'targets.category': '$it' }".bson), Aggregates.count())
+ }.let(::addAll)
+ families.map {
+ Facet("f$it", Aggregates.match("{ 'targets.family': '$it' }".bson), Aggregates.count())
+ }.let(::addAll)
+ platforms.map {
+ Facet("p$it", Aggregates.match("{ 'targets.id': '$it' }".bson), Aggregates.count())
+ }.let(::addAll)
+ }
+ val categoriesProjection = categories.joinToString(
+ prefix = "{",
+ postfix = "}",
+ separator = ","
+ ) { "'$it': { '$arrayElemAt': ['\$c$it.count', 0] }" }
+ val familiesProjection = families.joinToString(
+ prefix = "{",
+ postfix = "}",
+ separator = ","
+ ) { "'$it': { '$arrayElemAt': ['\$f$it.count', 0] }" }
+ val platformsProjection = platforms.joinToString(
+ prefix = "{",
+ postfix = "}",
+ separator = ","
+ ) { "'$it': { '$arrayElemAt': ['\$p$it.count', 0] }" }
+ val projection = Aggregates.project(
+ """
+ {
+ countTotal: { "$arrayElemAt": ["${'$'}total.count", 0] },
+ countByCategory: $categoriesProjection,
+ countByFamily: $familiesProjection,
+ countByPlatform: $platformsProjection,
+ }
+ """.trimIndent().bson
+ )
+ return runCatchingIO {
+ collection.aggregate(
+ facet(facets),
+ projection,
+ addFields(
+ Field("date", dateFormat.format(date)),
+ Field("ts", date.time),
+ )
+ ).first()!!
+ }.getOrThrow()
+ }
+}
diff --git a/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/StatisticRepository.kt b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/StatisticRepository.kt
new file mode 100644
index 0000000..cdd67c4
--- /dev/null
+++ b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/StatisticRepository.kt
@@ -0,0 +1,60 @@
+package dev.petuska.kodex.repository
+
+import dev.petuska.kodex.core.domain.LibrariesStatistic
+import dev.petuska.kodex.repository.util.runCatchingIO
+import org.litote.kmongo.MongoOperator.and
+import org.litote.kmongo.MongoOperator.gte
+import org.litote.kmongo.MongoOperator.lt
+import org.litote.kmongo.coroutine.CoroutineCollection
+
+class StatisticRepository(
+ val collection: CoroutineCollection,
+) {
+ private fun buildQuery(from: Long?, to: Long?): String? {
+ val fromQuery = from?.let {
+ """
+ {
+ ts: { $gte: $it }
+ }
+ """.trimIndent()
+ }
+ val toQuery = to?.let {
+ """
+ {
+ ts: { $lt: $it }
+ }
+ """.trimIndent()
+ }
+ val finalQuery = setOfNotNull(fromQuery, toQuery).takeIf { it.isNotEmpty() }?.let {
+ """
+ {
+ $and: [${it.joinToString(",")}]
+ }
+ """.trimIndent()
+ }
+
+ return finalQuery
+ }
+
+ suspend fun search(page: Int, size: Int, from: Long?, to: Long?): List {
+ val query = buildQuery(from, to)
+
+ return runCatchingIO {
+ val dbCall = query?.let(collection::find) ?: collection.find()
+ dbCall.skip(size * (page - 1)).limit(size)
+ dbCall.toList()
+ }.getOrThrow()
+ }
+
+ suspend fun count(from: Long?, to: Long?): Long {
+ return runCatchingIO { collection.countDocuments(buildQuery(from, to) ?: "{}") }.getOrThrow()
+ }
+
+ suspend fun findByDate(date: String): LibrariesStatistic? {
+ return runCatchingIO { collection.findOneById(date) }.getOrThrow()
+ }
+
+ suspend fun create(statistic: LibrariesStatistic) {
+ runCatchingIO { collection.save(statistic) }.getOrThrow()
+ }
+}
diff --git a/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/config/repositoryModule.kt b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/config/repositoryModule.kt
new file mode 100644
index 0000000..2260960
--- /dev/null
+++ b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/config/repositoryModule.kt
@@ -0,0 +1,49 @@
+package dev.petuska.kodex.repository.config
+
+import dev.petuska.kodex.core.domain.KotlinLibrary
+import dev.petuska.kodex.core.domain.LibrariesStatistic
+import dev.petuska.kodex.repository.LibraryRepository
+import dev.petuska.kodex.repository.StatisticRepository
+import kotlinx.coroutines.runBlocking
+import org.koin.core.module.dsl.onClose
+import org.koin.core.module.dsl.withOptions
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+import org.litote.kmongo.coroutine.CoroutineClient
+import org.litote.kmongo.coroutine.coroutine
+import org.litote.kmongo.reactivestreams.KMongo.createClient
+
+fun repositoryModule(
+ mongoString: String,
+ mongoDatabase: String,
+) = module {
+ single { createClient(mongoString).coroutine } withOptions {
+ onClose { it?.close() }
+ }
+ single(named("libraries")) {
+ get()
+ .getDatabase(mongoDatabase)
+ .getCollection("libraries")
+ .apply {
+ runBlocking {
+ createIndex(
+ """
+ {
+ group: "text",
+ name: "text",
+ description: "text"
+ }
+ """.trimIndent()
+ )
+ }
+ }
+ }
+ single(named("statistics")) {
+ get()
+ .getDatabase(mongoDatabase)
+ .getCollection("statistics")
+ }
+
+ single { LibraryRepository(get(named("libraries"))) }
+ single { StatisticRepository(get(named("statistics"))) }
+}
diff --git a/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/util/coroutines.kt b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/util/coroutines.kt
new file mode 100644
index 0000000..2a2f995
--- /dev/null
+++ b/lib/lib-repository/src/jvmMain/kotlin/dev/petuska/kodex/repository/util/coroutines.kt
@@ -0,0 +1,13 @@
+package dev.petuska.kodex.repository.util
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.slf4j.Logger
+
+suspend fun runCatchingIO(
+ logger: Logger? = null,
+ block: suspend CoroutineScope.() -> R
+): Result = runCatching {
+ withContext(Dispatchers.IO, block)
+}.onFailure { logger?.error(it.message, it) }
diff --git a/lib/lib-test/build.gradle.kts b/lib/lib-test/build.gradle.kts
new file mode 100644
index 0000000..7dfabaa
--- /dev/null
+++ b/lib/lib-test/build.gradle.kts
@@ -0,0 +1,37 @@
+plugins {
+ id("convention.library-js")
+ id("convention.library-jvm")
+ id("convention.library-android")
+}
+
+android {
+ namespace = "${rootProject.group}.${rootProject.name}.test"
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ api(libs.kotlin.test.common)
+ api(libs.kotlin.test.annotations.common)
+ api(libs.kotest.assertions.core)
+ api(libs.kotlinx.coroutines.test)
+ }
+ }
+ jvmMain {
+ dependencies {
+ api(libs.kotlin.test.junit)
+ }
+ }
+ androidMain {
+ dependencies {
+ api(libs.kotlin.test.junit)
+ }
+ }
+ jsMain {
+ dependencies {
+ api(libs.kotlin.test.js)
+ }
+ }
+ }
+}
diff --git a/lib/lib-test/src/commonMain/kotlin/dev/petuska/kodex/test/DynamicTestBuilder.kt b/lib/lib-test/src/commonMain/kotlin/dev/petuska/kodex/test/DynamicTestBuilder.kt
new file mode 100644
index 0000000..cd62b07
--- /dev/null
+++ b/lib/lib-test/src/commonMain/kotlin/dev/petuska/kodex/test/DynamicTestBuilder.kt
@@ -0,0 +1,50 @@
+package dev.petuska.kodex.test
+
+import io.kotest.matchers.shouldBe
+
+fun dynamicTests(builder: DynamicTestBuilder.() -> Unit) {
+ val tests = DynamicTestBuilder().apply(builder).tests
+ val results = tests.map { (name, test) ->
+ print("[TEST] $name")
+ val scope = DynamicTestBuilder.DynamicTestScope()
+ runCatching {
+ scope.test()
+ }.also {
+ if (it.isSuccess) {
+ println(" [PASS]")
+ scope.printAll()
+ } else {
+ println(" [FAIL]")
+ scope.printAll()
+ it.exceptionOrNull()?.printStackTrace()
+ }
+ }
+ }
+ results.none { it.isFailure } shouldBe true
+}
+
+class DynamicTestBuilder {
+ internal val tests = mutableMapOf Unit>()
+
+ class DynamicTestScope {
+ private val logs = mutableListOf<() -> Unit>()
+
+ internal fun printAll() {
+ logs.forEach { it() }
+ }
+
+ fun print(any: Any?) {
+ logs + { kotlin.io.print(any) }
+ }
+
+ fun println(any: Any? = null) {
+ logs + { kotlin.io.println(any) }
+ }
+ }
+
+ fun dynamicTest(name: String, test: DynamicTestScope.() -> Unit) {
+ tests[name] = test
+ }
+
+ operator fun String.invoke(test: DynamicTestScope.() -> Unit) = dynamicTest(this, test)
+}
diff --git a/lib/lib-ui/build.gradle.kts b/lib/lib-ui/build.gradle.kts
new file mode 100644
index 0000000..c1e90d9
--- /dev/null
+++ b/lib/lib-ui/build.gradle.kts
@@ -0,0 +1,40 @@
+import org.jetbrains.compose.ExperimentalComposeLibrary
+
+plugins {
+ id("convention.library-jvm")
+ id("convention.library-android")
+ id("convention.compose")
+}
+
+android {
+ namespace = "${rootProject.group}.${rootProject.name}.ui"
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ api(projects.lib.libClient)
+ @OptIn(ExperimentalComposeLibrary::class)
+ api(compose.components.resources)
+ api(compose.preview)
+ api(compose.material3)
+ }
+ }
+ jvmMain {
+ dependencies {
+ api(compose.desktop.currentOs)
+ }
+ }
+ androidMain {
+ dependencies {
+ }
+ }
+ commonTest {
+ dependencies {
+ @OptIn(ExperimentalComposeLibrary::class)
+ implementation(compose.uiTestJUnit4)
+ }
+ }
+ }
+}
diff --git a/lib/lib-ui/src/commonMain/kotlin/dev/petuska/kodex/ui/App.kt b/lib/lib-ui/src/commonMain/kotlin/dev/petuska/kodex/ui/App.kt
new file mode 100644
index 0000000..d475104
--- /dev/null
+++ b/lib/lib-ui/src/commonMain/kotlin/dev/petuska/kodex/ui/App.kt
@@ -0,0 +1,33 @@
+package dev.petuska.kodex.ui
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import dev.petuska.kodex.client.store.action.AppAction
+import dev.petuska.kodex.client.util.select
+import org.reduxkotlin.compose.rememberDispatcher
+
+@Composable
+fun App() {
+ val dispatch = rememberDispatcher()
+ val count by select { count }
+ val darkTheme by select { darkTheme }
+ MaterialTheme {
+ Surface(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ Column {
+ Button({ dispatch(AppAction.SetDarkTheme(!darkTheme)) }) { Text("Switch Theme") }
+ Button({ dispatch(AppAction.SetCount((count ?: 0) + 1L)) }) {
+ Text("Hello $count")
+ }
+ }
+ }
+ }
+}
diff --git a/scanner/Dockerfile b/scanner/Dockerfile
deleted file mode 100644
index c67a665..0000000
--- a/scanner/Dockerfile
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM gcr.io/distroless/java:11
-
-WORKDIR app
-COPY build/libs/*-jvm-*.jar scanner.jar
-
-ENTRYPOINT ["java", "-jar", "scanner.jar"]
diff --git a/scanner/Jcenter.MD b/scanner/Jcenter.MD
deleted file mode 100644
index fd6255f..0000000
--- a/scanner/Jcenter.MD
+++ /dev/null
@@ -1,164 +0,0 @@
-
-import kotlinx.serialization.Serializable
-
-import kotlinx.serialization.SerialName
-// Jcenter steps
-
-curl 'https://bintray.com/bintray/jcenter' \
- --data-raw 'offset=160&max=8&repoPath=%2Fbintray%2Fjcenter&sortBy=lowerCaseName&filterByPkgName=' \
- --compressed > out.html // Search by class="package-name"
-```html
-