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 -
-``` - -https://api.bintray.com/packages/mpetuska/lt.petuska/kvdom // system_ids: ["lt.petuska:kvdom"] -```json -{ - "name": "kvdom", - "repo": "lt.petuska", - "owner": "mpetuska", - "desc": "", - "labels": [ - "js", - "kotlin", - "mpp", - "vdom", - "wasm" - ], - "attribute_names": [ - "maturity" - ], - "licenses": [ - "Apache-2.0" - ], - "custom_licenses": [], - "followers_count": 0, - "created": "2020-01-30T13:07:57.952Z", - "website_url": "", - "issue_tracker_url": "https://gitlab.com/lt.petuska/kvdom/issues", - "linked_to_repos": [ - "jcenter" - ], - "permissions": [], - "versions": [ - "0.1.0-M2", - "0.1.0-M1", - "0.0.3", - "0.0.2" - ], - "latest_version": "0.1.0-M2", - "updated": "2020-07-28T08:57:00.741Z", - "rating_count": 0, - "system_ids": [ - "lt.petuska:kvdom-js", - "lt.petuska:kvdom", - "lt.petuska:kvdom-metadata", - "lt.petuska:kvdom-wasm32" - ], - "vcs_url": "https://gitlab.com/lt.petuska/kvdom", - "maturity": "Experimental" -} -``` - -https://dl.bintray.com/mpetuska/lt.petuska/lt/petuska/kvdom/maven-metadata.xml // 0.1.0-M2 -```xml - - lt.petuska - kvdom - 0.1.0-M2 - - 0.1.0-M2 - 0.1.0-M2 - - 0.0.2 - 0.0.3 - 0.1.0-M1 - 0.1.0-M2 - - 20200728085428 - - -``` - -https://dl.bintray.com/mpetuska/lt.petuska/lt/petuska/kvdom/0.1.0-M2/kvdom-0.1.0-M2.module -```json -{ - "formatVersion": "1.1", - "component": { - "group": "lt.petuska", - "module": "kvdom", - "version": "0.1.0-M2", - "attributes": { - "org.gradle.status": "release" - } - }, - "createdBy": { - "gradle": { - "version": "6.5.1", - "buildId": "ulyhyoo3zzgldchabzsfi7wfu4" - } - }, - "variants": [ - { - "name": "js-api", - "attributes": { - "org.gradle.usage": "kotlin-api", - "org.jetbrains.kotlin.js.compiler": "legacy", - "org.jetbrains.kotlin.platform.type": "js" - }, - "available-at": { - "url": "../../kvdom-js/0.1.0-M2/kvdom-js-0.1.0-M2.module", - "group": "lt.petuska", - "module": "kvdom-js", - "version": "0.1.0-M2" - } - }, - { - "name": "js-runtime", - "attributes": { - "org.gradle.usage": "kotlin-runtime", - "org.jetbrains.kotlin.js.compiler": "legacy", - "org.jetbrains.kotlin.platform.type": "js" - }, - "available-at": { - "url": "../../kvdom-js/0.1.0-M2/kvdom-js-0.1.0-M2.module", - "group": "lt.petuska", - "module": "kvdom-js", - "version": "0.1.0-M2" - } - }, - { - "name": "metadata-api", - "attributes": { - "org.gradle.usage": "kotlin-metadata", - "org.jetbrains.kotlin.platform.type": "common" - }, - "available-at": { - "url": "../../kvdom-metadata/0.1.0-M2/kvdom-metadata-0.1.0-M2.module", - "group": "lt.petuska", - "module": "kvdom-metadata", - "version": "0.1.0-M2" - } - }, - { - "name": "wasm32-api", - "attributes": { - "artifactType": "org.jetbrains.kotlin.klib", - "org.gradle.usage": "kotlin-api", - "org.jetbrains.kotlin.native.target": "wasm32", - "org.jetbrains.kotlin.platform.type": "native" - }, - "available-at": { - "url": "../../kvdom-wasm32/0.1.0-M2/kvdom-wasm32-0.1.0-M2.module", - "group": "lt.petuska", - "module": "kvdom-wasm32", - "version": "0.1.0-M2" - } - } - ] -} -``` diff --git a/scanner/build.gradle.kts b/scanner/build.gradle.kts deleted file mode 100644 index 83d8fca..0000000 --- a/scanner/build.gradle.kts +++ /dev/null @@ -1,93 +0,0 @@ -plugins { - kotlin("multiplatform") - kotlin("plugin.serialization") - id("org.jlleitschuh.gradle.ktlint") -} - -repositories { - mavenCentral() -} - -tasks { - withType { - useJUnitPlatform() - } - withType { - kotlinOptions { - jvmTarget = "${JavaVersion.VERSION_11}" - } - } -} - -kotlin { - jvm() - sourceSets { - named("jvmMain") { - dependencies { - implementation(project(":common")) - implementation("io.ktor:ktor-client-cio:_") - implementation("io.ktor:ktor-client-auth:_") - implementation("org.kodein.di:kodein-di:_") - implementation("org.jsoup:jsoup:_") - implementation("ch.qos.logback:logback-classic:_") - implementation(kotlin("reflect")) - implementation("org.jetbrains.kotlinx:kotlinx-cli:_") - } - } - named("jvmTest") { - dependencies { - implementation("io.kotest:kotest-runner-junit5:_") - } - } - all { - languageSettings.apply { - useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi") - useExperimentalAnnotation("io.ktor.util.KtorExperimentalAPI") - useExperimentalAnnotation("kotlin.time.ExperimentalTime") - useExperimentalAnnotation("kotlinx.coroutines.FlowPreview") - useExperimentalAnnotation("kotlinx.cli.ExperimentalCli") - } - } - } -} - -val mainClassName = "scanner.IndexKt" - -tasks { - val compileKotlinJvm by getting - val jvmProcessResources by getting - register("run") { - group = "run" - mainClass.set(mainClassName) - dependsOn(compileKotlinJvm, jvmProcessResources) - classpath = files( - configurations["jvmRuntimeClasspath"], - compileKotlinJvm.outputs, - jvmProcessResources.outputs - ) - } - named("jvmJar", Jar::class) { - duplicatesStrategy = DuplicatesStrategy.WARN - val classpath = configurations["jvmRuntimeClasspath"].files.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 "${GradleVersion.current()}", - "Created-From" to Git.headCommitHash - ) - } - - inputs.property("mainClassName", mainClassName) - inputs.files(classpath) - outputs.file(archiveFile) - } -} diff --git a/scanner/db.dockerfile b/scanner/db.dockerfile deleted file mode 100644 index f942099..0000000 --- a/scanner/db.dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM mongo AS builder -ARG input=out -COPY $input /home/data -RUN find /home/data -type f ! -name '_.json' -delete - -FROM mongo -COPY --from=builder /home/data /home/data -CMD for file in /home/data/*; do echo "Importing $file" && mongoimport --drop --host mongo --db main --collection ${file##*/} --file $file/_.json; done diff --git a/scanner/src/jvmMain/kotlin/scanner/client/AnchorClient.kt b/scanner/src/jvmMain/kotlin/scanner/client/AnchorClient.kt deleted file mode 100644 index a84a46a..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/client/AnchorClient.kt +++ /dev/null @@ -1,16 +0,0 @@ -package scanner.client - -import kamp.domain.MavenArtifact -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.text() - text.takeIf { it.isNotBlank() && !it.isBackLink() } - } -} diff --git a/scanner/src/jvmMain/kotlin/scanner/client/ArtifactoryClient.kt b/scanner/src/jvmMain/kotlin/scanner/client/ArtifactoryClient.kt deleted file mode 100644 index 68fa97f..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/client/ArtifactoryClient.kt +++ /dev/null @@ -1,13 +0,0 @@ -package scanner.client - -import io.ktor.client.HttpClient -import kamp.domain.MavenArtifactImpl -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("..") -} diff --git a/scanner/src/jvmMain/kotlin/scanner/client/JBossClient.kt b/scanner/src/jvmMain/kotlin/scanner/client/JBossClient.kt deleted file mode 100644 index c464a4b..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/client/JBossClient.kt +++ /dev/null @@ -1,13 +0,0 @@ -package scanner.client - -import io.ktor.client.HttpClient -import kamp.domain.MavenArtifactImpl -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) -} diff --git a/scanner/src/jvmMain/kotlin/scanner/client/MavenRepositoryClient.kt b/scanner/src/jvmMain/kotlin/scanner/client/MavenRepositoryClient.kt deleted file mode 100644 index 35d4dfc..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/client/MavenRepositoryClient.kt +++ /dev/null @@ -1,110 +0,0 @@ -package scanner.client - -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.util.date.GMTDate -import io.ktor.util.date.Month -import io.ktor.utils.io.core.Closeable -import kamp.domain.MavenArtifact -import kamp.domain.MavenArtifactImpl -import kotlinx.coroutines.coroutineScope -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import org.jsoup.nodes.Document -import scanner.domain.GradleModule -import scanner.util.LoggerDelegate -import scanner.util.asDocument -import scanner.util.supervisedAsync - -abstract class MavenRepositoryClient( - private val defaultRepositoryRootUrl: String, -) : Closeable { - protected abstract fun parsePage(page: Document): List? - protected abstract val client: HttpClient - protected abstract val json: Json - private val logger by LoggerDelegate() - - private val A.mavenModuleRootUrl: String - get() = "$defaultRepositoryRootUrl/${group.replace(".", "/")}/$name" - private val A.mavenModuleVersionUrl: String - get() = "$mavenModuleRootUrl/$releaseVersion" - - suspend fun getArtifactDetails(pathToMavenMetadata: String): MavenArtifactImpl? = coroutineScope { - val artifact = supervisedAsync { - val url = "$defaultRepositoryRootUrl$pathToMavenMetadata" - val pom = client.get(url).asDocument() - val doc = pom.getElementsByTag("metadata").first() ?: return@supervisedAsync null - try { - 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 - } - MavenArtifactImpl( - group = doc.selectFirst("groupId")?.text() ?: return@supervisedAsync null, - name = doc.selectFirst("artifactId")?.text() ?: return@supervisedAsync null, - latestVersion = doc.selectFirst("versioning>latest")?.text() - ?: doc.selectFirst("version")?.text() ?: return@supervisedAsync null, - releaseVersion = doc.selectFirst("versioning>release")?.text(), - versions = doc.selectFirst("versioning>versions")?.children()?.map { v -> v.text() }, - lastUpdated = lastUpdated, - ) - } catch (e: Exception) { - if (doc.selectFirst("plugins") == null) { - logger.warn("Unable to parse maven-metadata.xml from $url") - } - null - } - } - - artifact.await() - } - - suspend fun getGradleModule(artifact: A): GradleModule? = coroutineScope { - supervisedAsync { - val module = - client.get( - "${artifact.mavenModuleVersionUrl}/${artifact.name}-${artifact.releaseVersion}.module" - ) - json.decodeFromString(module) - } - .await() - } - - suspend fun getMavenPom(artifact: A): Document? = coroutineScope { - supervisedAsync { - client - .get( - "${artifact.mavenModuleVersionUrl}/${artifact.name}-${artifact.releaseVersion}.pom" - ) - .asDocument() - } - .await() - } - - suspend fun listRepositoryPath(path: String): List? = coroutineScope { - supervisedAsync { - client.get("$defaultRepositoryRootUrl${path.removeSuffix("/")}/").let { str -> - parsePage(str.asDocument())?.map { RepoItem(it, path) } - } - } - .await() - } - - override fun close() = client.close() - - class RepoItem(val value: String, path: String) { - val parentPath = "/${path.removeSuffix("/").removePrefix("/")}" - val path = "${if (parentPath == "/") "" else parentPath}/$value".removeSuffix("/") - val isDirectory = value.endsWith("/") - val isFile = !isDirectory - - override fun toString() = value - } -} diff --git a/scanner/src/jvmMain/kotlin/scanner/config/di.kt b/scanner/src/jvmMain/kotlin/scanner/config/di.kt deleted file mode 100644 index f0a99b0..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/config/di.kt +++ /dev/null @@ -1,78 +0,0 @@ -package scanner.config - -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.cio.CIO -import io.ktor.client.engine.cio.CIOEngineConfig -import io.ktor.client.features.HttpTimeout -import io.ktor.client.features.auth.Auth -import io.ktor.client.features.auth.providers.basic -import io.ktor.client.features.defaultRequest -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.json.serializer.KotlinxSerializer -import io.ktor.client.request.accept -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 -import scanner.domain.Repository -import scanner.processor.GradleModuleProcessor -import scanner.processor.PomProcessor -import scanner.service.MavenScannerService -import scanner.service.MavenScannerServiceImpl -import scanner.util.PrivateEnv -import scanner.util.prettyJson -import kotlin.time.minutes - -fun HttpClientConfig.baseConfig() { - val timeout = 2.5.minutes.inMilliseconds.toLong() - engine { requestTimeout = timeout } - defaultRequest { - contentType(ContentType.Application.Json) - accept(ContentType.Application.Json) - accept(ContentType.Text.Html) - } - install(HttpTimeout) { - requestTimeoutMillis = timeout - connectTimeoutMillis = timeout - socketTimeoutMillis = timeout - } - install(JsonFeature) { serializer = KotlinxSerializer(prettyJson) } -} - -val di = DI { - Repository.values().forEach { repo -> - bind(repo.alias) { provider { with(repo) { client(url) } } } - bind>(repo.alias) with - singleton { MavenScannerServiceImpl(instance(repo.alias), instance(), instance()) } - } - - bind { - singleton { - Json { - prettyPrint = true - ignoreUnknownKeys = true - } - } - } - bind { singleton { PomProcessor() } } - bind { singleton { GradleModuleProcessor() } } - bind { provider { HttpClient(CIO, HttpClientConfig::baseConfig) } } - bind("kamp") { - provider { - HttpClient(CIO) { - baseConfig() - install(Auth) { - basic { - username = PrivateEnv.ADMIN_USER - password = PrivateEnv.ADMIN_PASSWORD - } - } - } - } - } -} diff --git a/scanner/src/jvmMain/kotlin/scanner/domain/CLIOptions.kt b/scanner/src/jvmMain/kotlin/scanner/domain/CLIOptions.kt deleted file mode 100644 index e0341f2..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/domain/CLIOptions.kt +++ /dev/null @@ -1,32 +0,0 @@ -package scanner.domain - -data class CLIOptions( - val scanner: String, - val include: Set?, - val exclude: Set?, - val delayMS: Long?, - val workers: Int?, -) { - constructor( - scanner: String, - include: Set?, - from: Char?, - to: Char?, - exclude: Set?, - delayMS: Long?, - workers: Int?, - ) : this( - scanner, - ( - ( - include - ?: setOf() - ) + - (from?.let { to?.let { (from..to).toSet() } } ?: setOf()).map(Char::toString) - ) - .takeIf { it.isNotEmpty() }, - exclude, - delayMS, - workers - ) -} diff --git a/scanner/src/jvmMain/kotlin/scanner/domain/Repository.kt b/scanner/src/jvmMain/kotlin/scanner/domain/Repository.kt deleted file mode 100644 index beb5347..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/domain/Repository.kt +++ /dev/null @@ -1,45 +0,0 @@ -package scanner.domain - -import kamp.domain.MavenArtifactImpl -import org.kodein.di.DirectDIAware -import org.kodein.di.instance -import scanner.client.ArtifactoryClient -import scanner.client.JBossClient -import scanner.client.MavenRepositoryClient - -enum class Repository( - val alias: String, - val url: String, - val client: DirectDIAware.(url: String) -> MavenRepositoryClient, -) { - MAVEN_CENTRAL( - "mavenCentral", - "https://repo1.maven.org/maven2", - { ArtifactoryClient(it, instance(), instance()) } - ), - GRADLE_PLUGIN_PORTAL( - "gradlePluginPortal", - "https://plugins.gradle.org/m2", - { ArtifactoryClient(it, instance(), instance()) } - ), - SPRING( - "spring", - "https://repo.spring.io/release", - { ArtifactoryClient(it, instance(), instance()) } - ), - ATLASSIAN( - "atlassian", - "https://packages.atlassian.com/content/repositories/atlassian-public", - { ArtifactoryClient(it, instance(), instance()) } - ), - J_BOSS( - "jBoss", - "https://repository.jboss.org/nexus/content/repositories/releases", - { JBossClient(it, instance(), instance()) } - ), - HORTON_WORKS( - "hortonWorks", - "https://repo.hortonworks.com/content/repositories/releases", - { JBossClient(it, instance(), instance()) } - ), -} diff --git a/scanner/src/jvmMain/kotlin/scanner/index.kt b/scanner/src/jvmMain/kotlin/scanner/index.kt deleted file mode 100644 index 27797d2..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/index.kt +++ /dev/null @@ -1,61 +0,0 @@ -package scanner - -import kotlinx.cli.ArgParser -import kotlinx.cli.ArgType -import kotlinx.cli.multiple -import scanner.config.di -import scanner.domain.CLIOptions -import scanner.domain.Repository -import scanner.service.Orchestrator - -suspend fun main(args: Array) { - val parser = ArgParser("scanner") - val scanner by parser.argument( - type = ArgType.Choice(Repository.values().map(Repository::alias), { it }), - description = "Repository alias to scan for" - ) - - val from by parser.option( - type = ArgType.Choice(('a'..'z').toList(), { it[0] }), - shortName = "f", - description = "Repository root page filter start" - ) - val to by parser.option( - type = ArgType.Choice(('a'..'z').toList(), { it[0] }), - shortName = "t", - description = "Repository root page filter end" - ) - val include by parser - .option( - type = ArgType.String, - shortName = "i", - description = "Repository root page filter to include" - ) - .multiple() - val exclude by parser - .option( - type = ArgType.String, - shortName = "e", - description = "Repository root page filter to exclude" - ) - .multiple() - val delay by parser.option( - type = ArgType.Int, shortName = "d", description = "Worker processing delay in milliseconds" - ) - val workers by parser.option( - type = ArgType.Int, shortName = "w", description = "Concurrent worker count" - ) - parser.parse(args) - val cliOptions = - CLIOptions( - scanner = scanner, - include = include.toSet().takeIf { it.isNotEmpty() }, - from = from, - to = to, - exclude = exclude.toSet().takeIf { it.isNotEmpty() }, - delayMS = delay?.toLong(), - workers = workers - ) - - Orchestrator(di).run(scanner, cliOptions) -} diff --git a/scanner/src/jvmMain/kotlin/scanner/processor/GradleModuleProcessor.kt b/scanner/src/jvmMain/kotlin/scanner/processor/GradleModuleProcessor.kt deleted file mode 100644 index 983ef70..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/processor/GradleModuleProcessor.kt +++ /dev/null @@ -1,33 +0,0 @@ -package scanner.processor - -import kamp.domain.KotlinTarget -import scanner.domain.GradleModule - -class GradleModuleProcessor { - val kotlinVersion: String = "1.4.30" - - 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() - "androidJvm" -> KotlinTarget.JVM.Android() - "jvm" -> KotlinTarget.JVM.Java() - "js" -> - when (attrs.orgJetbrainsKotlinJsCompiler) { - "legacy" -> KotlinTarget.JS.Legacy() - "ir" -> KotlinTarget.JS.IR() - else -> null - } - "native" -> attrs.orgJetbrainsKotlinNativeTarget?.let { KotlinTarget.Native(it) } - else -> null - } - } - } - ?.toSet() -} diff --git a/scanner/src/jvmMain/kotlin/scanner/processor/PomProcessor.kt b/scanner/src/jvmMain/kotlin/scanner/processor/PomProcessor.kt deleted file mode 100644 index ab1f8bd..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/processor/PomProcessor.kt +++ /dev/null @@ -1,29 +0,0 @@ -package scanner.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 { - val url = - run { - it.selectFirst("url")?.text() - ?: it.selectFirst("connection")?.text() - ?: it.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/scanner/src/jvmMain/kotlin/scanner/service/MavenScannerService.kt b/scanner/src/jvmMain/kotlin/scanner/service/MavenScannerService.kt deleted file mode 100644 index 9163a60..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/service/MavenScannerService.kt +++ /dev/null @@ -1,71 +0,0 @@ -package scanner.service - -import io.ktor.utils.io.core.Closeable -import kamp.domain.KotlinMPPLibrary -import kamp.domain.MavenArtifact -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.receiveAsFlow -import scanner.client.MavenRepositoryClient -import scanner.domain.CLIOptions -import scanner.processor.GradleModuleProcessor -import scanner.processor.PomProcessor -import scanner.util.LoggerDelegate -import scanner.util.supervisedLaunch - -abstract class MavenScannerService : Closeable { - protected val logger by LoggerDelegate() - protected abstract val pomProcessor: PomProcessor - protected abstract val gradleModuleProcessor: GradleModuleProcessor - protected abstract val client: MavenRepositoryClient - protected abstract fun CoroutineScope.produceArtifacts( - cliOptions: CLIOptions? = null, - ): ReceiveChannel - - fun CoroutineScope.scanMavenArtefacts(cliOptions: CLIOptions? = null): Flow = - run { - logger.info( - "Scanning from repository root and filtering by ${cliOptions?.include ?: setOf()}, explicitly excluding ${cliOptions?.exclude ?: setOf()}" - ) - produceArtifacts(cliOptions) - } - .receiveAsFlow() - - suspend fun scanKotlinLibraries(cliOptions: CLIOptions? = null): Flow = - channelFlow { - val artefactsFlow = scanMavenArtefacts(cliOptions) - - repeat(Runtime.getRuntime().availableProcessors() * 2) { - supervisedLaunch { - artefactsFlow - .mapNotNull { artefact -> client.getGradleModule(artefact)?.let { artefact to it } } - .mapNotNull { (artefact, module) -> - with(gradleModuleProcessor) { - module.supportedTargets - ?.takeIf { module.isRootModule && !it.isNullOrEmpty() } - ?.let { - client.getMavenPom(artefact)?.let { pom -> - with(pomProcessor) { - KotlinMPPLibrary( - targets = it, - artifact = artefact, - description = pom.description, - website = pom.url, - scm = pom.scmUrl, - ) - } - } - } - } - } - .collect(::send) - } - } - } - - override fun close() = client.close() -} diff --git a/scanner/src/jvmMain/kotlin/scanner/service/MavenScannerServiceImpl.kt b/scanner/src/jvmMain/kotlin/scanner/service/MavenScannerServiceImpl.kt deleted file mode 100644 index 3caeb54..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/service/MavenScannerServiceImpl.kt +++ /dev/null @@ -1,83 +0,0 @@ -package scanner.service - -import kamp.domain.MavenArtifactImpl -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.produce -import kotlinx.coroutines.delay -import scanner.client.MavenRepositoryClient -import scanner.domain.CLIOptions -import scanner.processor.GradleModuleProcessor -import scanner.processor.PomProcessor -import scanner.util.supervisedLaunch -import kotlin.time.milliseconds -import kotlin.time.seconds - -class MavenScannerServiceImpl( - override val client: MavenRepositoryClient, - override val pomProcessor: PomProcessor, - override val gradleModuleProcessor: GradleModuleProcessor, -) : MavenScannerService() { - override fun CoroutineScope.produceArtifacts( - cliOptions: CLIOptions?, - ): ReceiveChannel = produce { - val pageChannel = Channel>(Channel.BUFFERED) - supervisedLaunch { - client - .listRepositoryPath("") - ?.filter { repoItem -> - val path = repoItem.path.removePrefix("/") - val included = - cliOptions?.include?.let { filter -> filter.any { path.startsWith(it) } } ?: true - val excluded = - cliOptions?.exclude?.let { filter -> filter.any { path.startsWith(it) } } ?: false - included && !excluded - } - ?.let { pageChannel.send(it) } - } - - // Tracker - supervisedLaunch { - var ticks = 0 - do { - delay(cliOptions?.delayMS?.milliseconds ?: 10.seconds) - if (pageChannel.isEmpty) { - logger.info("Page channel empty, ${5 - ticks} ticks remaining until close") - ticks++ - } else { - ticks = 0 - } - } while (ticks < 5) - logger.info("Closing page channel") - pageChannel.close() - logger.info("Closed page channel") - } - - // Workers - repeat(cliOptions?.workers ?: Runtime.getRuntime().availableProcessors() * 2) { - supervisedLaunch { - for (page in pageChannel) { - cliOptions?.delayMS?.let { delay(it.milliseconds) } - val artifactDetails = - page.find { it.value == "maven-metadata.xml" }?.let { - client.getArtifactDetails(it.path) - } - if (artifactDetails != null) { - logger.debug("Found MC artefact ${artifactDetails.group}:${artifactDetails.name}") - send(artifactDetails) - } else { - page.filter(MavenRepositoryClient.RepoItem::isDirectory).map { - supervisedLaunch { - client.listRepositoryPath(it.path)?.let { item -> - logger.debug("Scanned MC page ${it.path} and found ${item.size} children") - pageChannel.send(item) - } - } - } - } - } - } - } - } -} diff --git a/scanner/src/jvmMain/kotlin/scanner/service/Orchestrator.kt b/scanner/src/jvmMain/kotlin/scanner/service/Orchestrator.kt deleted file mode 100644 index 00b1dfe..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/service/Orchestrator.kt +++ /dev/null @@ -1,72 +0,0 @@ -package scanner.service - -import io.ktor.client.HttpClient -import io.ktor.client.request.post -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.buffer -import kotlinx.coroutines.flow.collect -import kotlinx.serialization.json.Json -import org.kodein.di.DI -import org.kodein.di.DIAware -import org.kodein.di.direct -import org.kodein.di.instance -import org.kodein.di.instanceOrNull -import scanner.domain.CLIOptions -import scanner.util.LoggerDelegate -import scanner.util.PrivateEnv -import scanner.util.supervisedLaunch -import kotlin.time.measureTime - -class Orchestrator(override val di: DI) : DIAware { - private val logger by LoggerDelegate() - private val json by di.instance() - - suspend fun run(scanner: String, cliOptions: CLIOptions? = null) { - val scannerService = di.direct.instanceOrNull>(scanner) - - scannerService?.let { - logger.info("Scanning repository: $scanner") - - val duration = measureTime { - coroutineScope { - supervisedLaunch { - logger.info("Starting $scanner scan") - val count = scanRepo(scannerService, cliOptions) - logger.info( - "Found $count kotlin modules with gradle metadata in $scanner repository filtered by ${cliOptions?.include ?: setOf()}, " + - "explicitly excluding ${cliOptions?.exclude ?: setOf()}" - ) - } - } - } - logger.info( - "Finished scanning $scanner in ${ - duration.toComponents { hours, minutes, seconds, nanoseconds -> - "${hours}h ${minutes}m $seconds.${nanoseconds}s" - } - }" - ) - } - ?: logger.error("ScannerService for $scanner not found") - } - - private suspend fun scanRepo( - scanner: MavenScannerService<*>, - cliOptions: CLIOptions? = null, - ): Int { - var count = 0 - val kamp by di.instance("kamp") - scanner.scanKotlinLibraries(cliOptions).buffer().collect { lib -> - coroutineScope { - supervisedLaunch { - count++ - logger.info("Found lib: ${lib.path}") - kamp.post("${PrivateEnv.API_URL}/api/libraries") { body = lib } - } - } - } - kamp.close() - scanner.close() - return count - } -} diff --git a/scanner/src/jvmMain/kotlin/scanner/util/LoggerDelegate.kt b/scanner/src/jvmMain/kotlin/scanner/util/LoggerDelegate.kt deleted file mode 100644 index 793d9e1..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/util/LoggerDelegate.kt +++ /dev/null @@ -1,17 +0,0 @@ -package scanner.util - -import org.slf4j.Logger -import org.slf4j.LoggerFactory.getLogger -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty -import kotlin.reflect.full.companionObject - -class LoggerDelegate : ReadOnlyProperty { - override fun getValue(thisRef: R, property: KProperty<*>): Logger { - val javaClass = - thisRef.javaClass.let { java -> - java.enclosingClass?.takeIf { it.kotlin.companionObject?.java == javaClass } ?: java - } - return getLogger(javaClass.name) - } -} diff --git a/scanner/src/jvmMain/kotlin/scanner/util/PrivateEnv.kt b/scanner/src/jvmMain/kotlin/scanner/util/PrivateEnv.kt deleted file mode 100644 index 1ea097a..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/util/PrivateEnv.kt +++ /dev/null @@ -1,9 +0,0 @@ -package scanner.util - -import kamp.util.Env - -object PrivateEnv : Env() { - 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/coroutines.kt b/scanner/src/jvmMain/kotlin/scanner/util/coroutines.kt deleted file mode 100644 index 3462bda..0000000 --- a/scanner/src/jvmMain/kotlin/scanner/util/coroutines.kt +++ /dev/null @@ -1,30 +0,0 @@ -package scanner.util - -import io.ktor.client.features.ClientRequestException -import io.ktor.http.HttpStatusCode -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.launch -import kotlinx.coroutines.supervisorScope - -fun CoroutineScope.supervisedAsync(block: suspend CoroutineScope.() -> R): Deferred = - async { - try { - supervisorScope(block) - } catch (e: Exception) { - if (e !is ClientRequestException || e.response.status != HttpStatusCode.NotFound) { - e.printStackTrace() - } - null - } - } - -fun CoroutineScope.supervisedLaunch(block: suspend CoroutineScope.() -> R): Job = launch { - try { - supervisorScope(block) - } catch (e: Exception) { - e.printStackTrace() - } -} diff --git a/scanner/src/jvmMain/resources/logback.xml b/scanner/src/jvmMain/resources/logback.xml deleted file mode 100644 index 502c6dd..0000000 --- a/scanner/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/scanner/src/jvmTest/kotlin/scanner/Sandbox.kt b/scanner/src/jvmTest/kotlin/scanner/Sandbox.kt deleted file mode 100644 index 58e1a6e..0000000 --- a/scanner/src/jvmTest/kotlin/scanner/Sandbox.kt +++ /dev/null @@ -1,5 +0,0 @@ -package scanner - -import io.kotest.core.spec.style.FunSpec - -class Sandbox : FunSpec({ test("sandbox") {} }) diff --git a/scanner/src/jvmTest/kotlin/scanner/processor/GradleModuleProcessorTest.kt b/scanner/src/jvmTest/kotlin/scanner/processor/GradleModuleProcessorTest.kt deleted file mode 100644 index ab4de71..0000000 --- a/scanner/src/jvmTest/kotlin/scanner/processor/GradleModuleProcessorTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package scanner.processor - -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.kotest.matchers.shouldBe -import kamp.domain.KotlinTarget -import scanner.domain.GradleModule -import scanner.testutil.parseJsonFile - -class GradleModuleProcessorTest : - FunSpec({ - val module = parseJsonFile("presenter-middleware-0.2.10.module") - - test("isRootModule") { - with(GradleModuleProcessor()) { - module.isRootModule shouldBe true - module.copy(component = module.component?.copy(url = "http")).isRootModule shouldBe false - } - } - - test("listSupportedTargets") { - with(GradleModuleProcessor()) { - val targets = module.supportedTargets - - targets shouldContainExactlyInAnyOrder - setOf( - KotlinTarget.JVM.Android(), - KotlinTarget.JVM.Java(), - KotlinTarget.JVM.Java(), - KotlinTarget.Native("ios_arm64"), - KotlinTarget.Native("ios_arm64"), - KotlinTarget.Native("ios_x64"), - KotlinTarget.Common(), - ) - - val targets1 = parseJsonFile("redux-kotlin-0.5.5.module").supportedTargets - targets1 shouldContainExactlyInAnyOrder - setOf( - KotlinTarget.Native("android_arm32"), - KotlinTarget.Native("android_arm64"), - KotlinTarget.Native("ios_arm32"), - KotlinTarget.Native("ios_arm64"), - KotlinTarget.Native("ios_x64"), - KotlinTarget.Native("watchos_x86"), - KotlinTarget.Native("watchos_arm64"), - KotlinTarget.Native("watchos_arm32"), - KotlinTarget.Native("wasm32"), - KotlinTarget.Native("tvos_x64"), - KotlinTarget.Native("tvos_arm64"), - KotlinTarget.Native("mingw_x86"), - KotlinTarget.Native("mingw_x64"), - KotlinTarget.Native("macos_x64"), - KotlinTarget.Native("linux_x64"), - KotlinTarget.Native("linux_mipsel32"), - KotlinTarget.Native("linux_mips32"), - KotlinTarget.Native("linux_arm64"), - KotlinTarget.Native("linux_arm32_hfp"), - KotlinTarget.Common(), - KotlinTarget.JVM.Java(), - KotlinTarget.JS.IR(), - KotlinTarget.JS.Legacy(), - ) - } - } - }) diff --git a/scanner/src/jvmTest/kotlin/scanner/testutil/resources.kt b/scanner/src/jvmTest/kotlin/scanner/testutil/resources.kt deleted file mode 100644 index 5715945..0000000 --- a/scanner/src/jvmTest/kotlin/scanner/testutil/resources.kt +++ /dev/null @@ -1,18 +0,0 @@ -package scanner.testutil - -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import org.jsoup.Jsoup - -inline fun Any.parseJsonFile(path: String) = - this::class.java.classLoader.getResourceAsStream(path).let { - Json { ignoreUnknownKeys = true }.decodeFromString(it.reader().use { r -> r.readText() }) - } - -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() }) - } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f3a184..6fc80c4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,27 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} plugins { - id("de.fayard.refreshVersions") version "0.40.1" + id("com.gradle.enterprise") version "3.12.3" } +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +includeBuild("build-conventions") -rootProject.name = "kamp" -include(":scanner", ":app", ":common") +rootProject.name = "kodex" +include( + ":lib:lib-test", + ":lib:lib-core", + ":lib:lib-repository", + ":lib:lib-client", + ":lib:lib-ui", +) +include( + ":app:app-cli", + ":app:app-server", +// ":app:app-client:app-client-common", + ":app:app-client:app-client-web", +) diff --git a/versions.properties b/versions.properties deleted file mode 100644 index 3632343..0000000 --- a/versions.properties +++ /dev/null @@ -1,38 +0,0 @@ -#### Dependencies and Plugin versions with their available updates. -#### Generated by `./gradlew refreshVersions` version 0.40.1 -#### -#### 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 - -plugin.com.github.jakemarsden.git-hooks=0.0.2 - -plugin.org.jlleitschuh.gradle.ktlint=10.1.0 - -version.ch.qos.logback..logback-classic=1.2.3 - -version.com.microsoft.azure..applicationinsights-web-auto=2.6.3 - -version.dev.fritz2..components=0.9.1 - -version.kodein.di=7.5.0 - -version.kotest=4.4.3 - -version.kotlin=1.4.32 - -version.kotlinx.cli=0.3.2 - -version.kotlinx.serialization=1.1.0 - - version.ktor=1.5.3 - -version.org.jsoup..jsoup=1.13.1 - -version.org.kodein.di..kodein-di=7.5.0 - -version.org.kodein.di..kodein-di-framework-ktor-server-jvm=7.5.0 - -version.org.litote.kmongo..kmongo-coroutine-serialization=4.2.5