diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..c2658d7d1b --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index cfab0a28ce..f759713fc3 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,3 +1,6 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`. + name: Benchmark Engine on: push: diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index fd26299b1f..f391c18324 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -1,3 +1,6 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`. + name: Changelog on: pull_request: diff --git a/.github/workflows/gui.yml b/.github/workflows/gui.yml index db13944f3b..007a0f7543 100644 --- a/.github/workflows/gui.yml +++ b/.github/workflows/gui.yml @@ -1,3 +1,6 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`. + name: GUI CI on: push: @@ -6,7 +9,7 @@ on: pull_request: {} workflow_dispatch: {} jobs: - enso-build-cli-ci-gen-job-build-backend-linux: + enso-build-ci-gen-job-build-backend-linux: name: Build Backend (linux) runs-on: - self-hosted @@ -68,7 +71,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-build-backend-macos: + enso-build-ci-gen-job-build-backend-macos: name: Build Backend (macos) runs-on: - macos-latest @@ -128,7 +131,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-build-backend-windows: + enso-build-ci-gen-job-build-backend-windows: name: Build Backend (windows) runs-on: - self-hosted @@ -190,7 +193,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-build-wasm-linux: + enso-build-ci-gen-job-build-wasm-linux: name: Build GUI (WASM) (linux) runs-on: - self-hosted @@ -252,7 +255,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-build-wasm-macos: + enso-build-ci-gen-job-build-wasm-macos: name: Build GUI (WASM) (macos) runs-on: - macos-latest @@ -312,7 +315,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-build-wasm-windows: + enso-build-ci-gen-job-build-wasm-windows: name: Build GUI (WASM) (windows) runs-on: - self-hosted @@ -374,7 +377,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-cancel-workflow-linux: + enso-build-ci-gen-job-cancel-workflow-linux: name: Cancel Previous Runs runs-on: - ubuntu-latest @@ -383,7 +386,7 @@ jobs: uses: styfle/cancel-workflow-action@0.9.1 with: access_token: ${{ github.token }} - enso-build-cli-ci-gen-job-lint-linux: + enso-build-ci-gen-job-lint-linux: name: Lint (linux) runs-on: - self-hosted @@ -445,7 +448,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-native-test-linux: + enso-build-ci-gen-job-native-test-linux: name: Native GUI tests (linux) runs-on: - self-hosted @@ -507,11 +510,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-package-ide-linux: + enso-build-ci-gen-job-package-ide-linux: name: Package IDE (linux) needs: - - enso-build-cli-ci-gen-job-build-backend-linux - - enso-build-cli-ci-gen-job-build-wasm-linux + - enso-build-ci-gen-job-build-backend-linux + - enso-build-ci-gen-job-build-wasm-linux runs-on: - self-hosted - Linux @@ -572,11 +575,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-package-ide-macos: + enso-build-ci-gen-job-package-ide-macos: name: Package IDE (macos) needs: - - enso-build-cli-ci-gen-job-build-backend-macos - - enso-build-cli-ci-gen-job-build-wasm-linux + - enso-build-ci-gen-job-build-backend-macos + - enso-build-ci-gen-job-build-wasm-linux runs-on: - macos-latest steps: @@ -640,11 +643,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-package-ide-windows: + enso-build-ci-gen-job-package-ide-windows: name: Package IDE (windows) needs: - - enso-build-cli-ci-gen-job-build-backend-windows - - enso-build-cli-ci-gen-job-build-wasm-linux + - enso-build-ci-gen-job-build-backend-windows + - enso-build-ci-gen-job-build-wasm-linux runs-on: - self-hosted - Windows @@ -707,7 +710,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-wasm-test-linux: + enso-build-ci-gen-job-wasm-test-linux: name: WASM GUI tests (linux) runs-on: - self-hosted diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7db152a508..d15ffb8d11 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,3 +1,6 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`. + name: Nightly Release on: schedule: diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index 92e080c5a1..937646a4ef 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -1,3 +1,6 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`. + name: Generate a new version on: workflow_dispatch: @@ -18,7 +21,7 @@ on: required: true type: string jobs: - enso-build-cli-ci-gen-promote-release-job-linux: + enso-build-ci-gen-promote-release-job-linux: name: Promote release (linux) runs-on: - self-hosted @@ -88,10 +91,10 @@ jobs: release: name: Release needs: - - enso-build-cli-ci-gen-promote-release-job-linux + - enso-build-ci-gen-promote-release-job-linux uses: ./.github/workflows/release.yml with: - version: ${{ needs.enso-build-cli-ci-gen-promote-release-job-linux.outputs.ENSO_VERSION }} + version: ${{ needs.enso-build-ci-gen-promote-release-job-linux.outputs.ENSO_VERSION }} secrets: inherit env: ENSO_BUILD_SKIP_VERSION_CHECK: "true" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d87e5f0ae..24f9e327c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,6 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`. + name: Release on: workflow_dispatch: @@ -13,7 +16,7 @@ on: required: true type: string jobs: - enso-build-cli-ci-gen-draft-release-linux: + enso-build-ci-gen-draft-release-linux: name: Create a release draft. runs-on: - self-hosted @@ -61,7 +64,7 @@ jobs: outputs: ENSO_RELEASE_ID: ${{ steps.prepare.outputs.ENSO_RELEASE_ID }} ENSO_VERSION: ${{ steps.prepare.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-job-build-wasm-linux: + enso-build-ci-gen-job-build-wasm-linux: name: Build GUI (WASM) (linux) runs-on: - self-hosted @@ -123,10 +126,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-deploy-gui-linux: + enso-build-ci-gen-job-deploy-gui-linux: name: Upload GUI to S3 (linux) needs: - - enso-build-cli-ci-gen-upload-ide-linux + - enso-build-ci-gen-upload-ide-linux runs-on: - self-hosted - Linux @@ -189,11 +192,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-deploy-runtime-linux: + enso-build-ci-gen-job-deploy-runtime-linux: name: Upload Runtime to ECR (linux) needs: - - enso-build-cli-ci-gen-draft-release-linux - - enso-build-cli-ci-gen-job-upload-backend-linux + - enso-build-ci-gen-draft-release-linux + - enso-build-ci-gen-job-upload-backend-linux runs-on: - self-hosted - Linux @@ -243,8 +246,8 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.ECR_PUSH_RUNTIME_ACCESS_KEY_ID }} AWS_DEFAULT_REGION: eu-west-1 AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY }} - ENSO_BUILD_ECR_REPOSITORY: runtime GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }} + crate_ECR_REPOSITORY: runtime timeout-minutes: 360 - if: failure() && runner.os == 'Windows' name: List files if failed (Windows) @@ -259,12 +262,12 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 env: - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-job-upload-backend-linux: + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + enso-build-ci-gen-job-upload-backend-linux: name: Upload Backend (linux) needs: - - enso-build-cli-ci-gen-draft-release-linux + - enso-build-ci-gen-draft-release-linux runs-on: - self-hosted - Linux @@ -326,12 +329,12 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 env: - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-job-upload-backend-macos: + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + enso-build-ci-gen-job-upload-backend-macos: name: Upload Backend (macos) needs: - - enso-build-cli-ci-gen-draft-release-linux + - enso-build-ci-gen-draft-release-linux runs-on: - macos-latest steps: @@ -391,12 +394,12 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 env: - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-job-upload-backend-windows: + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + enso-build-ci-gen-job-upload-backend-windows: name: Upload Backend (windows) needs: - - enso-build-cli-ci-gen-draft-release-linux + - enso-build-ci-gen-draft-release-linux runs-on: - self-hosted - Windows @@ -458,17 +461,17 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 env: - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-publish-release-linux: + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + enso-build-ci-gen-publish-release-linux: name: Publish release (linux) needs: - - enso-build-cli-ci-gen-draft-release-linux - - enso-build-cli-ci-gen-job-deploy-gui-linux - - enso-build-cli-ci-gen-job-deploy-runtime-linux - - enso-build-cli-ci-gen-upload-ide-linux - - enso-build-cli-ci-gen-upload-ide-macos - - enso-build-cli-ci-gen-upload-ide-windows + - enso-build-ci-gen-draft-release-linux + - enso-build-ci-gen-job-deploy-gui-linux + - enso-build-ci-gen-job-deploy-runtime-linux + - enso-build-ci-gen-upload-ide-linux + - enso-build-ci-gen-upload-ide-macos + - enso-build-ci-gen-upload-ide-windows runs-on: - self-hosted - Linux @@ -533,14 +536,14 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.ARTEFACT_S3_ACCESS_KEY_ID }} AWS_REGION: us-west-1 AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTEFACT_S3_SECRET_ACCESS_KEY }} - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-upload-ide-linux: + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + enso-build-ci-gen-upload-ide-linux: name: Build IDE (linux) needs: - - enso-build-cli-ci-gen-draft-release-linux - - enso-build-cli-ci-gen-job-build-wasm-linux - - enso-build-cli-ci-gen-job-upload-backend-linux + - enso-build-ci-gen-draft-release-linux + - enso-build-ci-gen-job-build-wasm-linux + - enso-build-ci-gen-job-upload-backend-linux runs-on: - self-hosted - Linux @@ -602,14 +605,14 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 env: - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-upload-ide-macos: + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + enso-build-ci-gen-upload-ide-macos: name: Build IDE (macos) needs: - - enso-build-cli-ci-gen-draft-release-linux - - enso-build-cli-ci-gen-job-build-wasm-linux - - enso-build-cli-ci-gen-job-upload-backend-macos + - enso-build-ci-gen-draft-release-linux + - enso-build-ci-gen-job-build-wasm-linux + - enso-build-ci-gen-job-upload-backend-macos runs-on: - macos-latest steps: @@ -674,14 +677,14 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 env: - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} - enso-build-cli-ci-gen-upload-ide-windows: + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + enso-build-ci-gen-upload-ide-windows: name: Build IDE (windows) needs: - - enso-build-cli-ci-gen-draft-release-linux - - enso-build-cli-ci-gen-job-build-wasm-linux - - enso-build-cli-ci-gen-job-upload-backend-windows + - enso-build-ci-gen-draft-release-linux + - enso-build-ci-gen-job-build-wasm-linux + - enso-build-ci-gen-job-upload-backend-windows runs-on: - self-hosted - Windows @@ -745,8 +748,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 env: - ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} - ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} + ENSO_RELEASE_ID: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }} + ENSO_VERSION: ${{ needs.enso-build-ci-gen-draft-release-linux.outputs.ENSO_VERSION }} env: ENSO_BUILD_SKIP_VERSION_CHECK: "true" ENSO_EDITION: ${{ inputs.version }} diff --git a/.github/workflows/scala-new.yml b/.github/workflows/scala-new.yml index dc714eb091..60bf52ae88 100644 --- a/.github/workflows/scala-new.yml +++ b/.github/workflows/scala-new.yml @@ -1,3 +1,6 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`. + name: Engine CI on: push: @@ -6,7 +9,7 @@ on: pull_request: {} workflow_dispatch: {} jobs: - enso-build-cli-ci-gen-job-cancel-workflow-linux: + enso-build-ci-gen-job-cancel-workflow-linux: name: Cancel Previous Runs runs-on: - ubuntu-latest @@ -15,7 +18,7 @@ jobs: uses: styfle/cancel-workflow-action@0.9.1 with: access_token: ${{ github.token }} - enso-build-cli-ci-gen-job-ci-check-backend-linux: + enso-build-ci-gen-job-ci-check-backend-linux: name: Engine (linux) runs-on: - self-hosted @@ -95,7 +98,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-ci-check-backend-macos: + enso-build-ci-gen-job-ci-check-backend-macos: name: Engine (macos) runs-on: - macos-latest @@ -173,7 +176,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 360 - enso-build-cli-ci-gen-job-ci-check-backend-windows: + enso-build-ci-gen-job-ci-check-backend-windows: name: Engine (windows) runs-on: - self-hosted diff --git a/.github/workflows/shader-tools.yml b/.github/workflows/shader-tools.yml new file mode 100644 index 0000000000..b9efaca796 --- /dev/null +++ b/.github/workflows/shader-tools.yml @@ -0,0 +1,150 @@ +# This file is auto-generated. Do not edit it manually! +# Edit the build\shader-tools\src\ci.rs module instead and run `cargo run --package enso-build-ci-gen`. + +name: Package Tools +on: + workflow_dispatch: {} +jobs: + run-create-linux-latest: + name: Run create (LinuxLatest) + runs-on: + - ubuntu-latest + steps: + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - id: step_0 + run: cargo run --package enso-build-shader-tools --bin create + env: + GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }} + timeout-minutes: 360 + outputs: + ENSO_RELEASE_ID: ${{ steps.step_0.outputs.ENSO_RELEASE_ID }} + timeout-minutes: 360 + run-package-linux-latest: + name: Run package (LinuxLatest) + needs: + - run-create-linux-latest + runs-on: + - ubuntu-latest + steps: + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - id: step_1 + run: cargo run --package enso-build-shader-tools --bin package + env: + GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }} + timeout-minutes: 360 + env: + ENSO_RELEASE_ID: ${{ needs.run-create-linux-latest.outputs.ENSO_RELEASE_ID }} + timeout-minutes: 360 + run-package-mac-os-latest: + name: Run package (MacOSLatest) + needs: + - run-create-linux-latest + runs-on: + - macos-latest + steps: + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - id: step_3 + run: cargo run --package enso-build-shader-tools --bin package + env: + GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }} + timeout-minutes: 360 + env: + ENSO_RELEASE_ID: ${{ needs.run-create-linux-latest.outputs.ENSO_RELEASE_ID }} + timeout-minutes: 360 + run-package-windows-latest: + name: Run package (WindowsLatest) + needs: + - run-create-linux-latest + runs-on: + - windows-latest + steps: + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - id: step_2 + run: cargo run --package enso-build-shader-tools --bin package + env: + GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }} + timeout-minutes: 360 + env: + ENSO_RELEASE_ID: ${{ needs.run-create-linux-latest.outputs.ENSO_RELEASE_ID }} + timeout-minutes: 360 + run-publish-linux-latest: + name: Run publish (LinuxLatest) + needs: + - run-create-linux-latest + - run-package-linux-latest + - run-package-mac-os-latest + - run-package-windows-latest + runs-on: + - ubuntu-latest + steps: + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - id: step_4 + run: cargo run --package enso-build-shader-tools --bin publish + env: + GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }} + timeout-minutes: 360 + env: + ENSO_RELEASE_ID: ${{ needs.run-create-linux-latest.outputs.ENSO_RELEASE_ID }} + timeout-minutes: 360 +env: + ENSO_BUILD_SKIP_VERSION_CHECK: "true" diff --git a/CHANGELOG.md b/CHANGELOG.md index a334fe0f41..e772f3665e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,10 +124,19 @@ with a shared implementation between the Desktop and Web versions of the IDE. - [Added a new component: Dropdown][3985]. A list of selectable labeled entries, suitable for single and multi-select scenarios. +- [Compile-time shader optimizer was implemented][4003]. It is capable of + extracting non-optimized shaders from the compiled WASM artifacts, running + stand-alone optimization toolchain (glslc, spirv-opt, spirv-cross), and + injecting optimized shaders back to WASM during its initialization process. + Unfortunately, it caused our theme system to stop working correctly, because + generated shaders differ per theme (only light theme is available, the dark + theme has been disabled). We will support multiple themes in the future, but + this is not on our priority list right now. [3857]: https://github.com/enso-org/enso/pull/3857 [3985]: https://github.com/enso-org/enso/pull/3985 [4047]: https://github.com/enso-org/enso/pull/4047 +[4003]: https://github.com/enso-org/enso/pull/4003 #### Enso Standard Library diff --git a/Cargo.lock b/Cargo.lock index ddef982731..b703ab4ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1178,7 +1178,7 @@ version = "0.1.0" dependencies = [ "Inflector", "serde", - "serde_yaml 0.8.26", + "serde_yaml", ] [[package]] @@ -1863,6 +1863,7 @@ dependencies = [ "derive_more", "dirs", "enso-build-base", + "ensogl-pack", "filetime", "flate2", "flume", @@ -1898,7 +1899,7 @@ dependencies = [ "semver 1.0.14", "serde", "serde_json", - "serde_yaml 0.9.14", + "serde_yaml", "shrinkwraprs 0.3.0", "strum", "sysinfo", @@ -1927,10 +1928,21 @@ dependencies = [ "futures-util", "serde", "serde_json", - "serde_yaml 0.9.14", + "serde_yaml", "tracing", ] +[[package]] +name = "enso-build-ci-gen" +version = "0.1.0" +dependencies = [ + "enso-build", + "enso-build-shader-tools", + "ide-ci", + "serde_yaml", + "tokio", +] + [[package]] name = "enso-build-cli" version = "0.1.0" @@ -1951,7 +1963,7 @@ dependencies = [ "octocrab", "serde", "serde_json", - "serde_yaml 0.9.14", + "serde_yaml", "strum", "tempfile", "tokio", @@ -1970,11 +1982,23 @@ dependencies = [ "proc-macro2", "quote", "regex", - "serde_yaml 0.9.14", + "serde_yaml", "shrinkwraprs 0.3.0", "syn", ] +[[package]] +name = "enso-build-shader-tools" +version = "0.1.0" +dependencies = [ + "html_parser", + "ide-ci", + "octocrab", + "regex", + "tempfile", + "tokio", +] + [[package]] name = "enso-build-utilities" version = "0.1.0" @@ -2664,6 +2688,7 @@ name = "ensogl-example-auto-layout" version = "0.1.0" dependencies = [ "ensogl-core", + "ensogl-hardcoded-theme", "wasm-bindgen", ] @@ -2953,6 +2978,8 @@ dependencies = [ name = "ensogl-hardcoded-theme" version = "0.1.0" dependencies = [ + "enso-prelude", + "enso-shapely", "ensogl-core", ] @@ -2979,6 +3006,20 @@ dependencies = [ "ensogl-text", ] +[[package]] +name = "ensogl-pack" +version = "0.1.0" +dependencies = [ + "enso-prelude", + "fs_extra", + "ide-ci", + "manifest-dir-macros", + "regex", + "tempfile", + "tokio", + "walkdir", +] + [[package]] name = "ensogl-scroll-area" version = "0.1.0" @@ -3760,6 +3801,20 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371fb981840150b1a54f7cb117bf6699f7466a1d4861daac33bc6fe2b5abea0" +[[package]] +name = "html_parser" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec016cabcf7c9c48f9d5fdc6b03f273585bfce640a0f47a69552039e92b1959a" +dependencies = [ + "pest", + "pest_derive", + "serde", + "serde_derive", + "serde_json", + "thiserror", +] + [[package]] name = "http" version = "0.2.8" @@ -4034,7 +4089,7 @@ dependencies = [ "semver 1.0.14", "serde", "serde_json", - "serde_yaml 0.9.14", + "serde_yaml", "sha2", "shrinkwraprs 0.3.0", "strum", @@ -4533,12 +4588,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -4596,6 +4645,18 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "manifest-dir-macros" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f08150cf2bab1fc47c2196f4f41173a27fcd0f684165e5458c0046b53a472e2f" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "matchers" version = "0.1.0" @@ -5961,9 +6022,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -6399,21 +6460,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.26" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" -dependencies = [ - "indexmap", - "ryu", - "serde", - "yaml-rust", -] - -[[package]] -name = "serde_yaml" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" dependencies = [ "indexmap", "itoa 1.0.3", @@ -8106,15 +8155,6 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "zeroize" version = "1.5.7" diff --git a/Cargo.toml b/Cargo.toml index 8c4e74e788..a70d1be3b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,14 +8,17 @@ members = [ "app/gui/enso-profiler-enso-data", "build/cli", "build/macros", - "build/enso-formatter", + "build/ci-gen", + "build/cli", "build/intellij-run-config-gen", "build/deprecated/rust-scripts", + "build/shader-tools", "lib/rust/*", "lib/rust/parser/src/syntax/tree/visitor", "lib/rust/parser/jni", "lib/rust/parser/generate-java", "lib/rust/parser/debug", + "lib/rust/ensogl/pack", "lib/rust/profiler/data", "lib/rust/profiler/demo-data", "integration-test", @@ -71,6 +74,11 @@ debug-assertions = true [workspace.dependencies] console-subscriber = "0.1.8" nix = "0.26.1" +octocrab = { git = "https://github.com/enso-org/octocrab", default-features = false, features = [ + "rustls" +] } +regex = "1.6.0" +serde_yaml = "0.9.16" serde-wasm-bindgen = "0.4.5" tokio = { version = "1.23.0", features = ["full", "tracing"] } tokio-util = { version = "0.7.4", features = ["full"] } diff --git a/app/gui/Cargo.toml b/app/gui/Cargo.toml index 92d0460e4b..acc4bcbee8 100644 --- a/app/gui/Cargo.toml +++ b/app/gui/Cargo.toml @@ -64,7 +64,7 @@ wasm-bindgen-futures = "0.4" websocket = "0.26.5" [dev-dependencies] -regex = { version = "1.3.6" } +regex = { workspace = true } wasm-bindgen-test = { workspace = true } [dependencies.web-sys] diff --git a/app/gui/analytics/src/remote_log.rs b/app/gui/analytics/src/remote_log.rs index 947e948881..62bb3a824e 100644 --- a/app/gui/analytics/src/remote_log.rs +++ b/app/gui/analytics/src/remote_log.rs @@ -19,7 +19,7 @@ mod js { #[wasm_bindgen(inline_js = " export function remote_log(msg, value) { try { - window.enso.remoteLog(msg,value) + window.ensoglApp.remoteLog(msg,value) } catch (error) { console.error(\"Error while logging message. \" + error ); } diff --git a/app/gui/config/src/lib.rs b/app/gui/config/src/lib.rs index ba03ed931e..f7138b3632 100644 --- a/app/gui/config/src/lib.rs +++ b/app/gui/config/src/lib.rs @@ -13,10 +13,8 @@ #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] -use enso_logger::*; use enso_prelude::*; -use enso_logger::DefaultWarningLogger as Logger; use ensogl::system::web; @@ -40,36 +38,35 @@ pub fn engine_version_requirement() -> semver::VersionReq { // ============ ensogl::read_args! { - [window_app_scope_name, window_app_scope_config_name] { - entry : String, - project : String, - project_manager : String, - language_server_rpc : String, - language_server_data : String, - namespace : String, - platform : web::platform::Platform, - frame : bool, - theme : String, - dark_theme : bool, - high_contrast : bool, - use_loader : bool, - wasm_url : String, - wasm_glue_url : String, - node_labels : bool, - crash_report_host : String, - data_gathering : bool, - mixpanel_token : String, - is_in_cloud : bool, - verbose : bool, - authentication_enabled : bool, - email : String, - application_config_url : String, - /// When profiling the application (e.g. with the `./run profile` command), this argument - /// chooses what is profiled. - test_workflow : String, - skip_min_version_check : bool, - preferred_engine_version : semver::Version, - enable_new_component_browser : bool, - emit_user_timing_measurements : bool, - } + application_config_url: String, + authentication_enabled: bool, + dark_theme: bool, + data_gathering: bool, + debug: bool, + email: Option, + emit_user_timing_measurements: bool, + enable_new_component_browser: bool, + enable_spector:bool, + entry: String, + frame: bool, + is_in_cloud: bool, + language_server_data: Option, + language_server_rpc: Option, + loader_download_to_init_ratio: f32, + max_before_main_entry_points_time_ms: f32, + namespace: Option, + node_labels: bool, + pkg_js_url: String, + pkg_wasm_url: String, + platform: Option, + preferred_engine_version: Option, + project: Option, + project_manager: Option, + shaders_url: String, + skip_min_version_check: bool, + /// When profiling the application (e.g. with the `./run profile` command), this argument + /// chooses what is profiled. + test_workflow: Option, + theme: String, + use_loader: bool, } diff --git a/app/gui/controller/double-representation/Cargo.toml b/app/gui/controller/double-representation/Cargo.toml index ccf4716da5..76b277c9a9 100644 --- a/app/gui/controller/double-representation/Cargo.toml +++ b/app/gui/controller/double-representation/Cargo.toml @@ -23,5 +23,5 @@ serde = { version = "1.0", features = ["derive"] } uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] } [dev-dependencies] -regex = { version = "1.3.6" } +regex = { workspace = true } wasm-bindgen-test = { workspace = true } diff --git a/app/gui/language/ast/impl/Cargo.toml b/app/gui/language/ast/impl/Cargo.toml index 3ab46277d6..19b106f23e 100644 --- a/app/gui/language/ast/impl/Cargo.toml +++ b/app/gui/language/ast/impl/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] derive_more = { version = "0.99.16" } failure = { version = "0.1.5" } lazy_static = { version = "1.4.0" } -regex = { version = "1" } +regex = { workspace = true } serde = { version = "1.0", features = ["derive", "rc"] } serde_json = { version = "1.0" } shrinkwraprs = { version = "0.2.1" } diff --git a/app/gui/src/config.rs b/app/gui/src/config.rs index 6cc8cc4489..f1f8d7c5cd 100644 --- a/app/gui/src/config.rs +++ b/app/gui/src/config.rs @@ -15,9 +15,9 @@ use enso_config::ARGS; // ============== #[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Fail)] +#[derive(Clone, Debug, Fail)] #[fail(display = "Missing program option: {}.", 0)] -pub struct MissingOption(&'static str); +pub struct MissingOption(String); #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Fail)] diff --git a/app/gui/src/ide/initializer.rs b/app/gui/src/ide/initializer.rs index 30f7d3dc40..2206958bc4 100644 --- a/app/gui/src/ide/initializer.rs +++ b/app/gui/src/ide/initializer.rs @@ -75,7 +75,7 @@ impl Initializer { config::InitialView::Project => view.switch_view_to_project(), } - if enso_config::ARGS.emit_user_timing_measurements.unwrap_or_default() { + if enso_config::ARGS.emit_user_timing_measurements { ensogl_app.display.connect_profiler_to_user_timing(); } let status_bar = view.status_bar().clone_ref(); @@ -249,7 +249,7 @@ pub fn register_views(app: &Application) { type PlaceholderEntryType = ensogl_component::list_view::entry::Label; app.views.register::>(); - if enso_config::ARGS.is_in_cloud.unwrap_or(false) { + if enso_config::ARGS.is_in_cloud { app.views.register::(); } } diff --git a/app/gui/src/lib.rs b/app/gui/src/lib.rs index dfc72f2c5d..e9dc8f8974 100644 --- a/app/gui/src/lib.rs +++ b/app/gui/src/lib.rs @@ -62,7 +62,6 @@ extern crate core; use prelude::*; -use wasm_bindgen::prelude::*; // ============== diff --git a/app/gui/src/profile_workflow.rs b/app/gui/src/profile_workflow.rs index 286b11407a..d1991efe26 100644 --- a/app/gui/src/profile_workflow.rs +++ b/app/gui/src/profile_workflow.rs @@ -1,7 +1,6 @@ //! Defines profilable workflows, and an entry point that runs a specified workflow. use crate::integration_test::prelude::*; -use wasm_bindgen::prelude::*; use enso_debug_api as debug_api; diff --git a/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs b/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs index 17101cf265..a04d40e340 100644 --- a/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs +++ b/app/gui/view/debug_scene/component-list-panel-view/src/lib.rs @@ -37,14 +37,12 @@ #![warn(unused_qualifications)] use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::application::Application; use ensogl_core::display; use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::object::ObjectOps; use ensogl_core::frp; -use ensogl_hardcoded_theme as theme; use ensogl_text as text; use ide_view_component_list_panel::grid; use ide_view_component_list_panel::grid::entry::icon; @@ -190,9 +188,6 @@ fn snap_to_pixel_offset(size: Vector2, scene_shape: &display::scene::Shape) -> V pub fn main() { ensogl_text_msdf::run_once_initialized(|| { let app = Application::new("root"); - theme::builtin::light::register(&app); - theme::builtin::light::enable(&app); - let world = &app.display; let scene = &world.default_scene; diff --git a/app/gui/view/debug_scene/documentation/src/lib.rs b/app/gui/view/debug_scene/documentation/src/lib.rs index 8764302263..d0cc899ecf 100644 --- a/app/gui/view/debug_scene/documentation/src/lib.rs +++ b/app/gui/view/debug_scene/documentation/src/lib.rs @@ -8,7 +8,6 @@ use ensogl::display::shape::*; use ensogl::prelude::*; -use wasm_bindgen::prelude::*; use enso_suggestion_database as suggestion_database; use enso_suggestion_database::doc_section; @@ -184,8 +183,6 @@ mod button { pub fn main() { ensogl_text_msdf::run_once_initialized(|| { let app = Application::new("root"); - ensogl_hardcoded_theme::builtin::light::register(&app); - ensogl_hardcoded_theme::builtin::light::enable(&app); let _registry = Registry::with_default_visualizations(); let wrapper = DatabaseWrapper::from_db(database()); diff --git a/app/gui/view/debug_scene/icons/src/lib.rs b/app/gui/view/debug_scene/icons/src/lib.rs index 3637eba0d0..c964c815b5 100644 --- a/app/gui/view/debug_scene/icons/src/lib.rs +++ b/app/gui/view/debug_scene/icons/src/lib.rs @@ -49,9 +49,6 @@ mod frame { #[allow(dead_code)] pub fn entry_point_searcher_icons() { let app = Application::new("root"); - ensogl_hardcoded_theme::builtin::dark::register(&app); - ensogl_hardcoded_theme::builtin::light::register(&app); - ensogl_hardcoded_theme::builtin::light::enable(&app); let world = app.display.clone(); mem::forget(app); let scene = &world.default_scene; diff --git a/app/gui/view/debug_scene/interface/src/lib.rs b/app/gui/view/debug_scene/interface/src/lib.rs index daa9f18f66..a968072a1e 100644 --- a/app/gui/view/debug_scene/interface/src/lib.rs +++ b/app/gui/view/debug_scene/interface/src/lib.rs @@ -19,7 +19,6 @@ use ast::crumbs::PatternMatchCrumb::*; use ast::crumbs::*; use ensogl::prelude::*; use span_tree::traits::*; -use wasm_bindgen::prelude::*; use enso_frp as frp; use ensogl::application::Application; diff --git a/app/gui/view/debug_scene/text-grid-visualization/src/lib.rs b/app/gui/view/debug_scene/text-grid-visualization/src/lib.rs index 077bbf2306..d41d4090bb 100644 --- a/app/gui/view/debug_scene/text-grid-visualization/src/lib.rs +++ b/app/gui/view/debug_scene/text-grid-visualization/src/lib.rs @@ -15,7 +15,6 @@ #![warn(unused_qualifications)] use ensogl::prelude::*; -use wasm_bindgen::prelude::*; use crate::text_visualization::TextGrid; @@ -86,10 +85,6 @@ fn init(app: &Application) { .expect("Failed to add font to HTML body."); let closure = ensogl::system::web::Closure::new(move |_| { - ensogl_hardcoded_theme::builtin::dark::register(&app); - ensogl_hardcoded_theme::builtin::light::register(&app); - ensogl_hardcoded_theme::builtin::light::enable(&app); - let world = &app.display; let scene = &world.default_scene; let camera = scene.camera(); @@ -126,6 +121,5 @@ fn init(app: &Application) { let _result = web::document.fonts().ready().unwrap().then(&closure); // This extends the lifetime of the closure which is what we want here. Otherwise, the closure // would be destroyed and the callback cannot be called. - #[allow(clippy::forget_non_drop)] mem::forget(closure); } diff --git a/app/gui/view/debug_scene/visualization/src/lib.rs b/app/gui/view/debug_scene/visualization/src/lib.rs index 735db0576a..d4678af472 100644 --- a/app/gui/view/debug_scene/visualization/src/lib.rs +++ b/app/gui/view/debug_scene/visualization/src/lib.rs @@ -15,7 +15,6 @@ #![warn(unused_qualifications)] use ensogl::prelude::*; -use wasm_bindgen::prelude::*; use ensogl::animation; use ensogl::application::Application; diff --git a/app/gui/view/graph-editor/src/builtin/visualization/native/text_visualization/grid_cache.rs b/app/gui/view/graph-editor/src/builtin/visualization/native/text_visualization/grid_cache.rs index dda560e3e9..32de24cebf 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/native/text_visualization/grid_cache.rs +++ b/app/gui/view/graph-editor/src/builtin/visualization/native/text_visualization/grid_cache.rs @@ -40,6 +40,7 @@ use super::GridVector; use super::GridWindow; + // ================= // === GridCache === // ================= diff --git a/app/gui/view/graph-editor/src/component/node.rs b/app/gui/view/graph-editor/src/component/node.rs index dc9288303a..6224a1489f 100644 --- a/app/gui/view/graph-editor/src/component/node.rs +++ b/app/gui/view/graph-editor/src/component/node.rs @@ -126,7 +126,7 @@ pub mod backdrop { use super::*; ensogl::shape! { - // Disable to allow interaction with the output port. + // Disabled to allow interaction with the output port. pointer_events = false; (style:Style, selection:f32) { @@ -732,7 +732,7 @@ impl Node { deselect_target <- input.deselect.constant(0.0); select_target <- input.select.constant(1.0); - selection.target <+ any(&deselect_target,&select_target); + selection.target <+ any(&deselect_target, &select_target); eval selection.value ((t) model.backdrop.selection.set(*t)); diff --git a/app/gui/view/graph-editor/src/component/node/output/area.rs b/app/gui/view/graph-editor/src/component/node/output/area.rs index 5f664cb750..1734a24d0f 100644 --- a/app/gui/view/graph-editor/src/component/node/output/area.rs +++ b/app/gui/view/graph-editor/src/component/node/output/area.rs @@ -238,7 +238,7 @@ impl Model { #[profile(Debug)] fn set_label(&self, content: impl Into) { - let str = if ARGS.node_labels.unwrap_or(true) { content.into() } else { default() }; + let str = if ARGS.node_labels { content.into() } else { default() }; self.label.set_content(str); } diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index 7a566bbf9f..4dff1cc276 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -104,8 +104,7 @@ const MAX_ZOOM: f32 = 1.0; fn traffic_lights_gap_width() -> f32 { let is_macos = ARGS.platform.map(|p| p.is_macos()) == Some(true); - let is_frameless = ARGS.frame == Some(false); - if is_macos && is_frameless { + if is_macos && !ARGS.frame { MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH + MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET } else { 0.0 diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index f9b2c361f8..de65e70dcd 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -146,7 +146,7 @@ pub enum SearcherVariant { impl SearcherVariant { fn new(app: &Application) -> Self { - if ARGS.enable_new_component_browser.unwrap_or(true) { + if ARGS.enable_new_component_browser { Self::ComponentBrowser(app.new_view::()) } else { Self::OldNodeSearcher(Rc::new(app.new_view::())) @@ -261,7 +261,7 @@ impl Model { let code_editor = app.new_view::(); let fullscreen_vis = default(); let debug_mode_popup = debug_mode_popup::View::new(app); - let window_control_buttons = ARGS.is_in_cloud.unwrap_or_default().as_some_from(|| { + let window_control_buttons = ARGS.is_in_cloud.as_some_from(|| { let window_control_buttons = app.new_view::(); display_object.add_child(&window_control_buttons); scene.layers.panel.add(&window_control_buttons); @@ -293,6 +293,9 @@ impl Model { } /// Sets style of IDE to the one defined by parameter `theme`. + /// + /// This does not change the EnsoGL theme. Changing it is not supported currently because + /// the theme is used for shader-precompilation. pub fn set_style(&self, theme: Theme) { match theme { Theme::Light => self.set_light_style(), @@ -301,12 +304,10 @@ impl Model { } fn set_light_style(&self) { - ensogl_hardcoded_theme::builtin::light::enable(&self.app); self.set_html_style("light-theme"); } fn set_dark_style(&self) { - ensogl_hardcoded_theme::builtin::dark::enable(&self.app); self.set_html_style("dark-theme"); } @@ -318,7 +319,7 @@ impl Model { if let Some(node) = self.graph_editor.nodes().get_cloned_ref(&node_id) { node.position().xy() } else { - error!("Trying to show searcher under nonexisting node"); + error!("Trying to show searcher under non existing node"); default() } } @@ -446,21 +447,11 @@ impl Deref for View { impl View { /// Constructor. pub fn new(app: &Application) -> Self { - ensogl_hardcoded_theme::builtin::dark::register(app); - ensogl_hardcoded_theme::builtin::light::register(app); - let theme = match ARGS.theme.as_deref() { - Some("dark") => { - ensogl_hardcoded_theme::builtin::dark::enable(app); - Theme::Dark - } - _ => { - ensogl_hardcoded_theme::builtin::light::enable(app); - Theme::Light - } + let theme = match ARGS.theme.as_ref() { + "dark" => Theme::Dark, + _ => Theme::Light, }; - display::style::javascript::expose_to_window(&app.themes); - let scene = app.display.default_scene.clone_ref(); let model = Model::new(app); let frp = Frp::new(); @@ -476,7 +467,6 @@ impl View { model.set_style(theme); // TODO[WD]: This should not be needed after the theme switching issue is implemented. // See: https://github.com/enso-org/ide/issues/795 - app.themes.update(); let input_change_delay = frp::io::timer::Timeout::new(network); if let Some(window_control_buttons) = &*model.window_control_buttons { diff --git a/app/ide-desktop/config.js b/app/ide-desktop/config.js deleted file mode 100644 index 5c408b09ad..0000000000 --- a/app/ide-desktop/config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Configuration options that are only used by the JavaScript part of the project. - */ - -export const defaultLogServerPort = 20060 -export const defaultLogServerHost = `localhost:${defaultLogServerPort}` diff --git a/app/ide-desktop/lib/client/src/index.js b/app/ide-desktop/lib/client/src/index.js index 57ef7fb9d6..ff9d2b5851 100644 --- a/app/ide-desktop/lib/client/src/index.js +++ b/app/ide-desktop/lib/client/src/index.js @@ -1,6 +1,5 @@ 'use strict' -import { defaultLogServerHost } from '../../../config.js' import assert from 'node:assert' import buildCfg from '../../../build.json' import Electron from 'electron' @@ -223,14 +222,6 @@ optParser.options('version', { describe: `Print the version`, }) -optParser.options('crash-report-host', { - describe: - 'The address of the server that will receive crash reports. ' + - 'Consists of a hostname, optionally followed by a ":" and a port number', - requiresArg: true, - default: defaultLogServerHost, -}) - optParser.options('data-gathering', { describe: 'Enable the sharing of any usage data', type: 'boolean', diff --git a/app/ide-desktop/lib/common/package.json b/app/ide-desktop/lib/common/package.json deleted file mode 100644 index ef77963d7c..0000000000 --- a/app/ide-desktop/lib/common/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "module", - "version": "1.0.0", - "author": { - "name": "Enso Team", - "email": "contact@enso.org" - }, - "homepage": "https://github.com/enso-org/ide", - "repository": { - "type": "git", - "url": "git@github.com:enso-org/ide.git" - }, - "bugs": { - "url": "https://github.com/enso-org/ide/issues" - }, - "name": "enso-studio-common", - "dependencies": { - "mime-types": "^2.1.35" - } -} diff --git a/app/ide-desktop/lib/common/src/animation.js b/app/ide-desktop/lib/common/src/animation.js deleted file mode 100644 index 5df3113a38..0000000000 --- a/app/ide-desktop/lib/common/src/animation.js +++ /dev/null @@ -1,18 +0,0 @@ -/// This module defines a simple set of animation utils. Follow the link to learn more: -/// https://easings.net/en - -// ================= -// === Animation === -// ================= - -export function ease_in_out_cubic(t) { - return t < 0.5 ? 4 * t * t * t : 1 - ((-2 * t + 2) * (-2 * t + 2) * (-2 * t + 2)) / 2 -} - -export function ease_in_out_quad(t) { - return t < 0.5 ? 2 * t * t : 1 - ((-2 * t + 2) * (-2 * t + 2)) / 2 -} - -export function ease_out_quart(t) { - return 1 - --t * t * t * t -} diff --git a/app/ide-desktop/lib/common/src/html_utils.js b/app/ide-desktop/lib/common/src/html_utils.js deleted file mode 100644 index 3c458a399b..0000000000 --- a/app/ide-desktop/lib/common/src/html_utils.js +++ /dev/null @@ -1,33 +0,0 @@ -// ================== -// === HTML Utils === -// ================== - -/// Remove the given node if it exists. -export function remove_node(node) { - if (node) { - node.parentNode.removeChild(node) - } -} - -/// Creates a new top-level div which occupy full size of its parent's space. -export function new_top_level_div() { - let node = document.createElement('div') - node.style.width = '100%' - node.style.height = '100%' - document.body.appendChild(node) - return node -} - -/// Log subsequent messages in a group. -export async function log_group_collapsed(msg, f) { - console.groupCollapsed(msg) - let out - try { - out = await f() - } catch (error) { - console.groupEnd() - throw error - } - console.groupEnd() - return out -} diff --git a/app/ide-desktop/lib/common/src/loader.js b/app/ide-desktop/lib/common/src/loader.js deleted file mode 100644 index 9433893ba2..0000000000 --- a/app/ide-desktop/lib/common/src/loader.js +++ /dev/null @@ -1,259 +0,0 @@ -import * as animation from './animation.js' -import * as html_utils from './html_utils.js' -import * as math from './math.js' -import * as svg from './svg.js' - -// ========================= -// === ProgressIndicator === -// ========================= - -let bg_color = 'rgb(249,250,251)' -let loader_color = '#303030' -let top_layer_index = 1000 - -/// Visual representation of the loader. -export class ProgressIndicator { - constructor(cfg) { - this.dom = html_utils.new_top_level_div() - this.dom.id = 'loader' - this.dom.style.position = 'fixed' - this.dom.style.top = 0 - this.dom.style.left = 0 - // In the Cloud UI, all layers are stacked, and the progress - // indicator must be placed at the top layer. - this.dom.style.zIndex = top_layer_index - - let center = document.createElement('div') - center.style.width = '100%' - center.style.height = '100%' - center.style.display = 'flex' - center.style.justifyContent = 'center' - center.style.alignItems = 'center' - this.dom.appendChild(center) - - let progress_bar_svg = this.init_svg() - let progress_bar = document.createElement('div') - progress_bar.innerHTML = progress_bar_svg - center.appendChild(progress_bar) - - this.progress_indicator = document.getElementById('progress_indicator') - this.progress_indicator_mask = document.getElementById('progress_indicator_mask') - this.progress_indicator_corner = document.getElementById('progress_indicator_corner') - - this.set(0) - this.set_opacity(0) - - if (cfg.use_loader) { - this.initialized = this.animate_show() - } else { - this.initialized = new Promise(resolve => { - resolve() - }) - } - this.animate_rotation() - this.destroyed = false - } - - /// Initializes the SVG view. - init_svg() { - let width = 128 - let height = 128 - let alpha = 0.9 - let inner_radius = 48 - let outer_radius = 60 - let mid_radius = (inner_radius + outer_radius) / 2 - let bar_width = outer_radius - inner_radius - - return svg.new_svg( - width, - height, - ` - - - - - - - - - - - - - - - ` - ) - } - - /// Destroys the component. Removes it from the stage and destroys attached callbacks. - destroy() { - html_utils.remove_node(this.dom) - this.destroyed = true - } - - /// Set the value of the loader [0..1]. - set(value) { - let min_angle = 0 - let max_angle = 359 - let angle_span = max_angle - min_angle - let mask_angle = (1 - value) * angle_span - min_angle - let corner_pos = math.polar_to_cartesian(54, -mask_angle) - this.progress_indicator_mask.setAttribute('d', svg.arc(128, -mask_angle)) - this.progress_indicator_corner.setAttribute('cx', corner_pos.x) - this.progress_indicator_corner.setAttribute('cy', corner_pos.y) - } - - /// Set the opacity of the loader. - set_opacity(val) { - this.progress_indicator.setAttribute('opacity', val) - } - - /// Set the rotation of the loader (angles). - set_rotation(val) { - this.progress_indicator.setAttribute('transform', `rotate(${val},0,0)`) - } - - /// Start show animation. It is used after the loader is created. - animate_show() { - let indicator = this - return new Promise(function (resolve, reject) { - let alpha = 0 - function show_step() { - if (alpha > 1) { - alpha = 1 - } - indicator.set_opacity(animation.ease_in_out_quad(alpha)) - alpha += 0.02 - if (alpha < 1) { - window.requestAnimationFrame(show_step) - } else { - resolve() - } - } - window.requestAnimationFrame(show_step) - }) - } - - /// Start the spinning animation. - animate_rotation() { - let indicator = this - let rotation = 0 - function rotate_step(timestamp) { - indicator.set_rotation(rotation) - rotation += 6 - if (!indicator.destroyed) { - window.requestAnimationFrame(rotate_step) - } - } - window.requestAnimationFrame(rotate_step) - } -} - -// ============== -// === Loader === -// ============== - -/// The main loader class. It connects to the provided fetch responses and tracks their status. -export class Loader { - constructor(resources, cfg) { - this.indicator = new ProgressIndicator(cfg) - this.total_bytes = 0 - this.received_bytes = 0 - this.download_speed = 0 - this.last_receive_time = performance.now() - this.initialized = this.indicator.initialized - this.cap_progress_at = 0.3 - - let self = this - this.done_resolve = null - this.done = new Promise(resolve => { - self.done_resolve = resolve - }) - - for (let resource of resources) { - this.total_bytes += parseInt(resource.headers.get('Content-Length')) - resource.clone().body.pipeTo(this.input_stream()) - } - - if (Number.isNaN(this.total_bytes)) { - console.error( - "Loader error. Server is not configured to send the 'Content-Length' metadata." - ) - this.total_bytes = 0 - } - } - - /// The current loading progress [0..1]. - value() { - if (this.total_bytes == 0) { - return 0.3 - } else { - return this.received_bytes / this.total_bytes - } - } - - /// Returns true if the loader finished. - is_done() { - return this.received_bytes == this.total_bytes - } - - /// Removes the loader with it's dom element. - destroy() { - this.indicator.destroy() - } - - /// Callback run on every new received byte stream. - on_receive(new_bytes) { - this.received_bytes += new_bytes - let time = performance.now() - let time_diff = time - this.last_receive_time - this.download_speed = new_bytes / time_diff - this.last_receive_time = time - - let percent = this.show_percentage_value() - let speed = this.show_download_speed() - let received = this.show_received_bytes() - console.log(`${percent}% (${received}) (${speed}).`) - - let indicator_progress = this.value() * this.cap_progress_at - this.indicator.set(indicator_progress) - if (this.is_done()) { - this.done_resolve() - } - } - - /// Download percentage value. - show_percentage_value() { - return Math.round(100 * this.value()) - } - - /// Download total size value. - show_total_bytes() { - return `${math.format_mb(this.total_bytes)} MB` - } - - /// Download received bytes value. - show_received_bytes() { - return `${math.format_mb(this.received_bytes)} MB` - } - - /// Download speed value. - show_download_speed() { - return `${math.format_mb(1000 * this.download_speed)} MB/s` - } - - /// Internal function for attaching new fetch responses. - input_stream() { - let loader = this - return new WritableStream({ - write(t) { - loader.on_receive(t.length) - }, - }) - } -} diff --git a/app/ide-desktop/lib/common/src/math.js b/app/ide-desktop/lib/common/src/math.js deleted file mode 100644 index 4121b50a26..0000000000 --- a/app/ide-desktop/lib/common/src/math.js +++ /dev/null @@ -1,19 +0,0 @@ -/// This module defines a common math operations. - -// ============ -// === Math === -// ============ - -/// Converts the polar coordinates to cartesian ones. -export function polar_to_cartesian(radius, angle_degrees) { - let angle = ((angle_degrees - 90) * Math.PI) / 180.0 - return { - x: radius * Math.cos(angle), - y: radius * Math.sin(angle), - } -} - -/// Format bytes as megabytes with a single precision number. -export function format_mb(bytes) { - return Math.round((10 * bytes) / (1024 * 1024)) / 10 -} diff --git a/app/ide-desktop/lib/common/src/svg.js b/app/ide-desktop/lib/common/src/svg.js deleted file mode 100644 index 966763ca91..0000000000 --- a/app/ide-desktop/lib/common/src/svg.js +++ /dev/null @@ -1,30 +0,0 @@ -/// This module defines a set of utils for generating and modifying the SVG images. - -import * as math from './math.js' - -// =========== -// === SVG === -// =========== - -/// Defines a new SVG with the provided source. -export function new_svg(width, height, str) { - return ` - - ${str} - ` -} - -/// Returns SVG code for an arc with a defined radius and angle. -export function arc(radius, end_angle) { - let start_angle = 0 - if (end_angle < 0) { - start_angle = end_angle - end_angle = 0 - } - let start = math.polar_to_cartesian(radius, end_angle) - let end = math.polar_to_cartesian(radius, start_angle) - let large_arc = end_angle - start_angle <= 180 ? '0' : '1' - return `M 0 0 L ${start.x} ${start.y} A ${radius} ${radius} 0 ${large_arc} 0 ${end.x} ${end.y}` -} diff --git a/app/ide-desktop/lib/content/esbuild-config.ts b/app/ide-desktop/lib/content/esbuild-config.ts index eea68edf47..dbd074fa96 100644 --- a/app/ide-desktop/lib/content/esbuild-config.ts +++ b/app/ide-desktop/lib/content/esbuild-config.ts @@ -33,13 +33,18 @@ export const thisPath = path.resolve(dirname(fileURLToPath(import.meta.url))) // === Environment variables === // ============================= -export const wasm_path = require_env('ENSO_BUILD_GUI_WASM') -export const js_glue_path = require_env('ENSO_BUILD_GUI_JS_GLUE') +/** List of files to be copied from WASM artifacts. */ +export const wasm_artifacts = require_env('ENSO_BUILD_GUI_WASM_ARTIFACTS') + +/** Directory with assets. Its contents are to be copied. */ export const assets_path = require_env('ENSO_BUILD_GUI_ASSETS') /** Path where bundled files are output. */ export const output_path = path.resolve(require_env('ENSO_BUILD_GUI'), 'assets') +/** The main JS bundle to load WASM and JS wasm-pack bundles. */ +export const ensogl_app_path = require_env('ENSO_BUILD_GUI_ENSOGL_APP') + // =================== // === Git process === // =================== @@ -67,7 +72,7 @@ const always_copied_files = [ path.resolve(thisPath, 'src', 'run.js'), path.resolve(thisPath, 'src', 'style.css'), path.resolve(thisPath, 'src', 'docsStyle.css'), - wasm_path, + ...wasm_artifacts.split(path.delimiter), ] /** @@ -88,14 +93,14 @@ async function* files_to_copy_provider() { const config: esbuild.BuildOptions = { bundle: true, - entryPoints: ['src/index.ts', 'src/wasm_imports.js'], + entryPoints: ['src/index.ts'], outdir: output_path, outbase: 'src', plugins: [ plugin_yaml.yamlPlugin({}), NodeModulesPolyfillPlugin(), NodeGlobalsPolyfillPlugin({ buffer: true, process: true }), - aliasPlugin({ wasm_rust_glue: js_glue_path }), + aliasPlugin({ ensogl_app: ensogl_app_path }), timePlugin(), copy_plugin.create(files_to_copy_provider), ], @@ -108,6 +113,7 @@ const config: esbuild.BuildOptions = { minify: true, metafile: true, publicPath: '/assets', + platform: 'node', incremental: true, color: true, logOverride: { diff --git a/app/ide-desktop/lib/content/firebase.yaml b/app/ide-desktop/lib/content/firebase.yaml deleted file mode 100644 index 37dd904ed1..0000000000 --- a/app/ide-desktop/lib/content/firebase.yaml +++ /dev/null @@ -1,8 +0,0 @@ -authDomain: "enso-org.firebaseapp.com" -projectId: "enso-org" -storageBucket: "enso-org.appspot.com" -messagingSenderId: "451746386966" -appId: "1:451746386966:web:558a832abe486208d61137" -measurementId: "G-W11ZNCQ476" -clientId: "451746386966-u5piv17hgvnimpq5ic5p60liekcqmqmu.apps.googleusercontent.com" -apiKey: "AIzaSyA99Ap9yN-RmNeb6dYIiUYPTCamLAZxTQ8" diff --git a/app/ide-desktop/lib/content/package.json b/app/ide-desktop/lib/content/package.json index 6031c0ce10..b45cd6848c 100644 --- a/app/ide-desktop/lib/content/package.json +++ b/app/ide-desktop/lib/content/package.json @@ -21,12 +21,10 @@ }, "dependencies": { "@types/semver": "^7.3.9", - "enso-studio-common": "1.0.0", - "firebase": "^9.14.0", - "firebaseui": "^6.0.2", "html-loader": "^4.2.0", "mixpanel-browser": "2.45.0", - "enso-gui-server": "^1.0.0" + "enso-gui-server": "^1.0.0", + "@types/mixpanel-browser": "^2.38.0" }, "devDependencies": { "@esbuild-plugins/node-modules-polyfill": "^0.1.4", diff --git a/app/ide-desktop/lib/content/src/index.html b/app/ide-desktop/lib/content/src/index.html index b515b9b8ed..4252f15b6f 100644 --- a/app/ide-desktop/lib/content/src/index.html +++ b/app/ide-desktop/lib/content/src/index.html @@ -29,11 +29,6 @@ Enso - diff --git a/app/ide-desktop/lib/content/src/index.ts b/app/ide-desktop/lib/content/src/index.ts index 9f192fe27d..92a3b72d6a 100644 --- a/app/ide-desktop/lib/content/src/index.ts +++ b/app/ide-desktop/lib/content/src/index.ts @@ -2,519 +2,56 @@ /// user with a visual representation of this process (welcome screen). It also implements a view /// allowing to choose a debug rendering test from. -// @ts-ignore -import * as loader_module from 'enso-studio-common/src/loader' -// @ts-ignore -import * as html_utils from 'enso-studio-common/src/html_utils' // @ts-ignore import globalConfig from '../../../../gui/config.yaml' -// @ts-ignore -import { defaultLogServerHost } from '../../../config' -// @ts-ignore -import assert from 'assert' -// @ts-ignore import buildCfg from '../../../build.json' - -// @ts-ignore -import firebase from 'firebase/compat/app' -// @ts-ignore -import 'firebase/auth' -// @ts-ignore -import firebase_config from '../firebase.yaml' - +import * as app from 'ensogl_app' import * as semver from 'semver' -import { SemVer, Comparator } from 'semver' -import * as https from 'https' +const logger = app.log.logger +const config = app.config -const authInfo = 'auth-info' +// ============= +// === Fetch === +// ============= -// ================== -// === Global API === -// ================== - -class ContentApi { - main: (inputConfig: any) => Promise - private logger: MixpanelLogger - - initLogging(config: Config) { - if (config.data_gathering) { - this.logger = new MixpanelLogger(config.mixpanel_token) - if (ok(config.email)) { - this.logger.identify(config.email) - } - } - } - remoteLog(event: string, data?: any) { - if (this.logger) { - this.logger.log(event, data) - } - } +const Timeout = (time: number) => { + let controller = new AbortController() + setTimeout(() => controller.abort(), time * 1000) + return controller } -const API = new ContentApi() - -// @ts-ignore -window[globalConfig.windowAppScopeName] = API - -// ======================== -// === Content Download === -// ======================== - -let incorrect_mime_type_warning = ` -'WebAssembly.instantiateStreaming' failed because your server does not serve wasm with -'application/wasm' MIME type. Falling back to 'WebAssembly.instantiate' which is slower. -` - -async function wasm_instantiate_streaming( - resource: Response, - imports: WebAssembly.Imports -): Promise { - try { - return WebAssembly.instantiateStreaming(resource, imports) - } catch (e) { - if (resource.headers.get('Content-Type') !== 'application/wasm') { - console.warn(`${incorrect_mime_type_warning} Original error:\n`, e) - const buffer = await resource.arrayBuffer() - return WebAssembly.instantiate(buffer, imports) +async function fetchTimeout(url: string, timeout: number): Promise { + return fetch(url, { signal: Timeout(10).signal }).then(response => { + const statusCodeOK = 200 + if (response.status === statusCodeOK) { + return response.json() } else { - throw "Server not configured to serve WASM with 'application/wasm' mime type." - } - } -} - -/// Downloads the WASM binary and its dependencies. Displays loading progress bar unless provided -/// with `{use_loader:false}` option. -async function download_content(config: { wasm_glue_url: RequestInfo; wasm_url: RequestInfo }) { - let wasm_glue_fetch = await fetch(config.wasm_glue_url) - let wasm_fetch = await fetch(config.wasm_url) - let loader = new loader_module.Loader([wasm_glue_fetch, wasm_fetch], config) - - // TODO [mwu] - // Progress indication for WASM loading is hereby capped at 30%. - // The remaining 70% is meant for IDE initialization. Currently we have no means of tracking - // it, so we keep spinner running at 30% to denote ongoing initialization. - // See https://github.com/enso-org/ide/issues/1237 for an immediate reason. - // See https://github.com/enso-org/ide/issues/1105 for a broader context. - loader.cap_progress_at = 0.3 - - loader.done.then(() => { - console.groupEnd() - console.log('Download finished. Finishing WASM compilation.') - }) - - let download_size = loader.show_total_bytes() - let download_info = `Downloading WASM binary and its dependencies (${download_size}).` - let wasm = await html_utils.log_group_collapsed(download_info, async () => { - let wasm_glue_js = await wasm_glue_fetch.text() - let wasm_glue = Function('let exports = {};' + wasm_glue_js + '; return exports')() - console.log('WASM dependencies loaded.') - console.log('Starting online WASM compilation.') - - // @ts-ignore - return await wasm_glue.init(wasm_fetch) - }) - - console.log('WASM Compiled.') - - await loader.initialized - return { wasm, loader } -} - -// ==================== -// === Debug Screen === -// ==================== - -/// The name of the main scene in the WASM binary. -let main_entry_point = 'ide' - -/// Prefix name of each scene defined in the WASM binary. -let wasm_entry_point_pfx = 'entry_point_' - -/// Displays a debug screen which allows the user to run one of predefined debug examples. -function show_debug_screen(wasm: any, msg: string) { - API.remoteLog('show_debug_screen') - let names = [] - for (let fn of Object.getOwnPropertyNames(wasm)) { - if (fn.startsWith(wasm_entry_point_pfx)) { - let name = fn.replace(wasm_entry_point_pfx, '') - names.push(name) - } - } - - if (msg === '' || msg === null || msg === undefined) { - msg = '' - } - let debug_screen_div = html_utils.new_top_level_div() - let newDiv = document.createElement('div') - let newContent = document.createTextNode(msg + 'Available entry points:') - let ul = document.createElement('ul') - debug_screen_div.style.position = 'absolute' - debug_screen_div.style.zIndex = 1 - newDiv.appendChild(newContent) - debug_screen_div.appendChild(newDiv) - newDiv.appendChild(ul) - - for (let name of names) { - let li = document.createElement('li') - let a = document.createElement('a') - let linkText = document.createTextNode(name) - ul.appendChild(li) - a.appendChild(linkText) - a.title = name - a.href = '?entry=' + name - li.appendChild(a) - } -} - -// ==================== -// === Scam Warning === -// ==================== - -function printScamWarning() { - let headerCSS = ` - color : white; - background : crimson; - display : block; - border-radius : 8px; - font-weight : bold; - padding: 10px 20px 10px 20px; - ` - let headerCSS1 = headerCSS + 'font-size : 46px;' - let headerCSS2 = headerCSS + 'font-size : 20px;' - let msgCSS = 'font-size:16px;' - - let msg1 = - 'This is a browser feature intended for developers. If someone told you to ' + - 'copy-paste something here, it is a scam and will give them access to your ' + - 'account and data.' - let msg2 = - 'See https://github.com/enso-org/enso/blob/develop/docs/security/selfxss.md for more ' + - 'information.' - console.log('%cStop!', headerCSS1) - console.log('%cYou may be victim of a scam!', headerCSS2) - console.log('%c' + msg1, msgCSS) - console.log('%c' + msg2, msgCSS) -} - -// ====================== -// === Remote Logging === -// ====================== - -class MixpanelLogger { - private readonly mixpanel: any - - constructor(mixpanel_token: string) { - this.mixpanel = require('mixpanel-browser') - this.mixpanel.init(mixpanel_token, { api_host: 'https://api-eu.mixpanel.com' }, '') - } - - log(event: string, data: any) { - if (this.mixpanel) { - event = MixpanelLogger.trim_message(event) - if (data !== undefined && data !== null) { - data = MixpanelLogger.trim_message(JSON.stringify(data)) - this.mixpanel.track(event, { data }) - } else { - this.mixpanel.track(event) - } - } else { - console.warn(`Failed to log the event '${event}'.`) - } - } - - identify(uniqueId: string) { - this.mixpanel.identify(uniqueId) - } - - static trim_message(message: string) { - const MAX_MESSAGE_LENGTH = 500 - let trimmed = message.substr(0, MAX_MESSAGE_LENGTH) - if (trimmed.length < message.length) { - trimmed += '...' - } - return trimmed - } -} - -// ====================== -// === Logs Buffering === -// ====================== - -const logsFns = ['log', 'info', 'debug', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd'] - -class LogRouter { - private buffer: any[] - private readonly raw: {} - autoFlush: boolean - - constructor() { - this.buffer = [] - this.raw = {} - this.autoFlush = true - // @ts-ignore - console.autoFlush = true - for (let name of logsFns) { - // @ts-ignore - this.raw[name] = console[name] - // @ts-ignore - console[name] = (...args) => { - this.handle(name, args) - } - } - } - - auto_flush_on() { - this.autoFlush = true - // @ts-ignore - console.autoFlush = true - for (let { name, args } of this.buffer) { - // @ts-ignore - this.raw[name](...args) - } - this.buffer = [] - } - - handle(name: string, args: any[]) { - if (this.autoFlush) { - // @ts-ignore - this.raw[name](...args) - } else { - this.buffer.push({ name, args }) - } - - // The following code is just a hack to discover if the logs start with `[E]` which - // indicates errors from Rust logger. - if (name == 'error') { - this.handleError(...args) - } else if (name == 'log') { - let firstArg = args[0] - if (firstArg !== undefined) { - if (!(typeof firstArg === 'string' || firstArg instanceof String)) { - firstArg = firstArg.toString() - } - if (firstArg.startsWith('%c')) { - let firstArgBody = firstArg.slice(2) - let bodyStartIndex = firstArgBody.indexOf('%c') - if (bodyStartIndex !== -1) { - let body = firstArgBody.slice(bodyStartIndex + 3) - let is_error = body.startsWith('[E]') - if (is_error) { - this.handleError(body) - } - } - } - } - } - } - - handleError(...args: any[]) { - API.remoteLog('error', args) - } -} - -let logRouter = new LogRouter() - -function hideLogs() { - console.log('All subsequent logs will be hidden. Eval `showLogs()` to reveal them.') - logRouter.autoFlush = false - // @ts-ignore - console.autoFlush = false -} - -function showLogs() { - logRouter.auto_flush_on() -} - -// @ts-ignore -window.showLogs = showLogs - -// ====================== -// === Crash Handling === -// ====================== - -function initCrashHandling() { - setupCrashDetection() - if (previousCrashMessageExists()) { - showCrashBanner(getPreviousCrashMessage()) - clearPreviousCrashMessage() - } -} - -const crashMessageStorageKey = 'crash-message' - -function previousCrashMessageExists() { - return sessionStorage.getItem(crashMessageStorageKey) !== null -} - -function getPreviousCrashMessage() { - return sessionStorage.getItem(crashMessageStorageKey) -} - -function storeLastCrashMessage(message: string) { - sessionStorage.setItem(crashMessageStorageKey, message) -} - -function clearPreviousCrashMessage() { - sessionStorage.removeItem(crashMessageStorageKey) -} - -// === Crash detection === - -function setupCrashDetection() { - // This will only have an effect if the GUI is running in V8. - // (https://v8.dev/docs/stack-trace-api#compatibility) - Error.stackTraceLimit = 100 - - window.addEventListener('error', function (event) { - // We prefer stack traces over plain error messages but not all browsers produce traces. - if (ok(event.error) && ok(event.error.stack)) { - handleCrash(event.error.stack) - } else { - handleCrash(event.message) + throw new Error(`Failed to fetch '${url}'. Response status: ${response.status}.`) } }) - window.addEventListener('unhandledrejection', function (event) { - // As above, we prefer stack traces. - // But here, `event.reason` is not even guaranteed to be an `Error`. - handleCrash(event.reason.stack || event.reason.message || 'Unhandled rejection') - }) } -function handleCrash(message: string) { - API.remoteLog('crash', message) - if (document.getElementById(crashBannerId) === null) { - storeLastCrashMessage(message) - location.reload() - } else { - // @ts-ignore - for (let element of [...document.body.childNodes]) { - // @ts-ignore - if (element.id !== crashBannerId) { - element.remove() - } - } - document.getElementById(crashBannerContentId).insertAdjacentHTML( - 'beforeend', - `
-
A second error occurred. This time, the IDE will not automatically restart.
` - ) - } -} +// =============== +// === Version === +// =============== -// === Crash recovery === - -// Those IDs should be the same that are used in index.html. -const crashBannerId = 'crash-banner' -const crashBannerContentId = 'crash-banner-content' -const crashReportButtonId = 'crash-report-button' -const crashBannerCloseButtonId = 'crash-banner-close-button' - -function showCrashBanner(message: string) { - document.body.insertAdjacentHTML( - 'afterbegin', - `
- -
- - An internal error occurred and the Enso IDE has been restarted. -
-
` - ) - - const banner = document.getElementById(crashBannerId) - const content = document.getElementById(crashBannerContentId) - const report_button = document.getElementById(crashReportButtonId) - const close_button = document.getElementById(crashBannerCloseButtonId) - - report_button.onclick = async _event => { - try { - await reportCrash(message) - content.textContent = 'Thank you, the crash was reported.' - } catch (e) { - content.textContent = 'The crash could not be reported.' - } - } - close_button.onclick = () => { - banner.remove() - } -} - -async function reportCrash(message: string) { - // @ts-ignore - const crashReportHost = API[globalConfig.windowAppScopeConfigName].crash_report_host - await fetch(`http://${crashReportHost}/`, { - method: 'POST', - mode: 'no-cors', - headers: { - 'Content-Type': 'text/plain', - }, - body: message, - }) -} - -// ===================== -// === Version Check === -// ===================== - -// An error with the payload. -class ErrorDetails { - public readonly message: string - public readonly payload: any - - constructor(message: string, payload: any) { - this.message = message - this.payload = payload - } -} - -/// Utility methods helping to work with the versions. -class Versions { +class Version { /// Development version. - static devVersion = new SemVer('0.0.0') + static dev = new semver.SemVer('0.0.0') static devPrerelease = 'dev' /// Version of the `client` js package. - static ideVersion = new SemVer(buildCfg.version, { loose: true }) + static ide = new semver.SemVer(buildCfg.version, { loose: true }) - static isDevVersion(): boolean { - const clientVersion = Versions.ideVersion - const releaseDev = clientVersion.compareMain(Versions.devVersion) === 0 - const prereleaseDev = clientVersion.prerelease.toString().includes(Versions.devPrerelease) + static isDev(): boolean { + const clientVersion = Version.ide + const releaseDev = clientVersion.compareMain(Version.dev) === 0 + const prereleaseDev = clientVersion.prerelease.toString().includes(Version.devPrerelease) return releaseDev || prereleaseDev } } -/// Fetch the application config from the provided url. -async function fetchApplicationConfig(url: string) { - const statusCodeOK = 200 - - return new Promise((resolve: any, reject: any) => { - https.get(url, res => { - const statusCode = res.statusCode - if (statusCode !== statusCodeOK) { - reject(new ErrorDetails('Request failed.', { url, statusCode })) - return - } - - res.setEncoding('utf8') - let rawData = '' - - res.on('data', (chunk: any) => (rawData += chunk)) - - res.on('end', () => { - try { - resolve(JSON.parse(rawData)) - } catch (e) { - reject(e) - } - }) - - res.on('error', (e: any) => reject(e)) - }) - }) -} - /// Return `true` if the current application version is still supported /// and `false` otherwise. /// @@ -524,363 +61,21 @@ async function fetchApplicationConfig(url: string) { /// one of the compared versions does not match the semver scheme, it returns /// `true`. async function checkMinSupportedVersion(config: Config) { - if (config.skip_min_version_check === true) { + if (config.skipMinVersionCheck.value === true) { return true } try { - const appConfig: any = await fetchApplicationConfig(config.application_config_url) - const clientVersion = Versions.ideVersion + const appConfig: any = await fetchTimeout(config.applicationConfigUrl.value, 300) const minSupportedVersion = appConfig.minimumSupportedVersion - const comparator = new Comparator(`>=${minSupportedVersion}`) - return comparator.test(Versions.ideVersion) + const comparator = new semver.Comparator(`>=${minSupportedVersion}`) + return comparator.test(Version.ide) } catch (e) { console.error('Minimum version check failed.', e) return true } } -// ====================== -// === Authentication === -// ====================== - -class FirebaseAuthentication { - protected readonly config: any - public readonly firebaseui: any - public readonly ui: any - - public authCallback: any - - constructor(authCallback: any) { - this.firebaseui = require('firebaseui') - this.config = firebase_config - // initialize Firebase - firebase.initializeApp(this.config) - // create HTML markup - this.createHtml() - // initialize Firebase UI - this.ui = new this.firebaseui.auth.AuthUI(firebase.auth()) - this.ui.disableAutoSignIn() - this.authCallback = authCallback - firebase.auth().onAuthStateChanged((user: any) => { - if (ok(user)) { - if (this.hasEmailAuth(user) && !user.emailVerified) { - document.getElementById('user-email-not-verified').style.display = 'block' - this.handleSignedOutUser() - } else { - this.handleSignedInUser(user) - } - } else { - this.handleSignedOutUser() - } - }) - } - - protected hasEmailAuth(user: any): boolean { - const emailProviderId = firebase.auth.EmailAuthProvider.PROVIDER_ID - const hasEmailProvider = user.providerData.some( - (data: any) => data.providerId === emailProviderId - ) - const hasOneProvider = user.providerData.length === 1 - return hasOneProvider && hasEmailProvider - } - - protected getUiConfig() { - return { - callbacks: { - // Called when the user has been successfully signed in. - signInSuccessWithAuthResult: (authResult: any, redirectUrl: any) => { - if (ok(authResult.user)) { - switch (authResult.additionalUserInfo.providerId) { - case firebase.auth.EmailAuthProvider.PROVIDER_ID: - if (authResult.user.emailVerified) { - this.handleSignedInUser(authResult.user) - } else { - authResult.user.sendEmailVerification() - document.getElementById( - 'user-email-not-verified' - ).style.display = 'block' - this.handleSignedOutUser() - } - break - - default: - } - } - // Do not redirect. - return false - }, - }, - signInOptions: [ - { - provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID, - // Required to enable ID token credentials for this provider. - clientId: this.config.clientId, - }, - firebase.auth.GithubAuthProvider.PROVIDER_ID, - { - provider: firebase.auth.EmailAuthProvider.PROVIDER_ID, - // Whether the display name should be displayed in Sign Up page. - requireDisplayName: false, - }, - ], - } - } - - protected handleSignedOutUser() { - document.getElementById('auth-container').style.display = 'block' - this.ui.start('#firebaseui-container', this.getUiConfig()) - } - - protected handleSignedInUser(user: any) { - document.getElementById('auth-container').style.display = 'none' - this.authCallback(user) - } - - /// Create the HTML markup. - /// - /// ``` - ///
- ///
- ///
- ///

Sign in to Enso

- ///
- ///

Enso lets you create interactive data workflows. In order to share them, you need an account. In alpha/beta versions, this account is required.

- ///
- ///
- ///
- ///
- ///
- ///
- /// Verification link is sent. You can sign in after verifying your email. - ///
- ///
- ///
- /// This version is no longer supported. Please download a new one. - ///
- ///
- /// ``` - protected createHtml() { - const authContainer = 'auth-container' - const authHeader = 'auth-header' - const authText = 'auth-text' - const firebaseuiContainer = 'firebaseui-container' - const userSignedOut = 'user-signed-out' - const userEmailNotVerified = 'user-email-not-verified' - - const authHeaderText = document.createTextNode('Sign in to Enso') - const authTextText = document.createTextNode( - 'Enso lets you create interactive data workflows. In order to share them, you need an account. In alpha/beta versions, this account is required.' - ) - const userEmailNotVerifiedText = document.createTextNode( - 'Verification link is sent. You can sign in after verifying your email.' - ) - - let root = document.getElementById('root') - - // div#auth-container - let authContainerDiv = document.createElement('div') - authContainerDiv.id = authContainer - authContainerDiv.style.display = 'none' - // div.auth-header - let authHeaderDiv = document.createElement('div') - authHeaderDiv.className = authHeader - // div.auth-header/h1 - let authHeaderH1 = document.createElement('h1') - authHeaderH1.appendChild(authHeaderText) - // div.auth-header/div#auth-text - let authHeaderTextDiv = document.createElement('div') - authHeaderTextDiv.className = authText - authHeaderTextDiv.appendChild(authTextText) - - authHeaderDiv.appendChild(authHeaderH1) - authHeaderDiv.appendChild(authHeaderTextDiv) - - // div#user-signed-out - let userSignedOutDiv = document.createElement('div') - userSignedOutDiv.id = userSignedOut - let firebaseuiContainerDiv = document.createElement('div') - firebaseuiContainerDiv.id = firebaseuiContainer - userSignedOutDiv.appendChild(firebaseuiContainerDiv) - - // div#user-email-not-verified - let userEmailNotVerifiedDiv = document.createElement('div') - userEmailNotVerifiedDiv.id = userEmailNotVerified - userEmailNotVerifiedDiv.className = authInfo - userEmailNotVerifiedDiv.appendChild(userEmailNotVerifiedText) - - authContainerDiv.appendChild(authHeaderDiv) - authContainerDiv.appendChild(userSignedOutDiv) - authContainerDiv.appendChild(userEmailNotVerifiedDiv) - - root.appendChild(authContainerDiv) - } -} - -// ======================== -// === Main Entry Point === -// ======================== - -function style_root() { - let root = document.getElementById('root') - root.style.backgroundColor = 'rgb(249,250,251)' -} - -/// Waits for the window to finish its show animation. It is used when the website is run in -/// Electron. Please note that it returns immediately in the web browser. -async function windowShowAnimation() { - // @ts-ignore - await window.showAnimation -} - -function disableContextMenu() { - document.body.addEventListener('contextmenu', e => { - e.preventDefault() - }) -} - -function ok(value: any) { - return value !== null && value !== undefined -} - -class Config { - public entry: string = undefined - public project: string = undefined - public project_manager: string = undefined - public language_server_rpc: string = undefined - public language_server_data: string = undefined - public namespace: string = undefined - public platform: string = undefined - public frame: boolean = false - public theme: string = undefined - public dark_theme: boolean = false - public high_contrast: boolean = false - public use_loader: boolean = true - public wasm_url: string = '/assets/ide.wasm' - public wasm_glue_url: string = '/assets/wasm_imports.js' - public node_labels: boolean = true - public crash_report_host: string = defaultLogServerHost - public data_gathering: boolean = true - public mixpanel_token: string = '5b541aeab5e08f313cdc1d1bbebc12ac' - public is_in_cloud: boolean = false - public verbose: boolean = false - public authentication_enabled: boolean = true - public email: string = undefined - public application_config_url: string = - 'https://raw.githubusercontent.com/enso-org/ide/develop/config.json' - public test_workflow: string = undefined - public skip_min_version_check: boolean = Versions.isDevVersion() - public preferred_engine_version: SemVer = Versions.ideVersion - public enable_new_component_browser: boolean = true - public emit_user_timing_measurements: boolean = false - - updateFromObject(other: any) { - if (!ok(other)) { - return - } - for (let key of Object.keys(this)) { - let self: any = this - let otherVal = other[key] - let selfVal = self[key] - if (ok(otherVal)) { - if (typeof selfVal === 'boolean') { - let val = tryAsBoolean(otherVal) - if (val === null) { - console.error( - `Invalid value for ${key}: ${otherVal}. Expected boolean. Reverting to the default value of ` - ) - } else { - self[key] = val - } - } else if (selfVal instanceof SemVer) { - let val = semver.parse(otherVal) - if (val === null) { - console.error(`Invalid value for ${key}: ${otherVal}. Expected semver.`) - } else { - self[key] = val - } - } else { - self[key] = tryAsString(otherVal) - } - } - } - } -} - -/// Check whether the value is a string with value `"true"`/`"false"`, if so, return the -// appropriate boolean instead. Otherwise, return the original value. -function parseBooleanOrLeaveAsIs(value: any): any { - if (value === 'true') { - return true - } - if (value === 'false') { - return false - } - return value -} - -function tryAsBoolean(value: any): boolean | null { - value = parseBooleanOrLeaveAsIs(value) - return typeof value == 'boolean' ? value : null -} - -function tryAsString(value: any): string { - return value.toString() -} - -/// Main entry point. Loads WASM, initializes it, chooses the scene to run. -async function runEntryPoint(config: Config) { - // @ts-ignore - API[globalConfig.windowAppScopeConfigName] = config - - API.initLogging(config) - - // Build data injected during the build process. See `webpack.config.js` for the source. - // @ts-ignore - const hash = GIT_HASH - API.remoteLog('git_hash', { hash }) - // @ts-ignore - const buildInfo = BUILD_INFO - API.remoteLog('build_information', buildInfo) - // @ts-ignore - const status = GIT_STATUS - API.remoteLog('git_status', { status }) - - //initCrashHandling() - style_root() - printScamWarning() - /// Only hide logs in production, but show them when running a development version. - if (!Versions.isDevVersion()) { - hideLogs() - } - disableContextMenu() - - let entryTarget = ok(config.entry) ? config.entry : main_entry_point - config.use_loader = config.use_loader && entryTarget === main_entry_point - - API.remoteLog('window_show_animation') - await windowShowAnimation() - API.remoteLog('download_content') - let { wasm, loader } = await download_content(config) - API.remoteLog('wasm_loaded') - if (entryTarget) { - let fn_name = wasm_entry_point_pfx + entryTarget - let fn = wasm[fn_name] - if (fn) { - // Loader will be removed by IDE after its initialization. - // All other code paths need to call `loader.destroy()`. - fn() - } else { - loader.destroy() - show_debug_screen(wasm, "Unknown entry point '" + entryTarget + "'. ") - } - } else { - loader.destroy() - show_debug_screen(wasm, '') - } -} - -function createVersionCheckHtml() { - // div#version-check +function displayDeprecatedVersionDialog() { const versionCheckText = document.createTextNode( 'This version is no longer supported. Please download a new one.' ) @@ -888,32 +83,154 @@ function createVersionCheckHtml() { let root = document.getElementById('root') let versionCheckDiv = document.createElement('div') versionCheckDiv.id = 'version-check' - versionCheckDiv.className = authInfo + versionCheckDiv.className = 'auth-info' versionCheckDiv.style.display = 'block' versionCheckDiv.appendChild(versionCheckText) root.appendChild(versionCheckDiv) } -API.main = async function (inputConfig: any) { - const urlParams = new URLSearchParams(window.location.search) - // @ts-ignore - const urlConfig = Object.fromEntries(urlParams.entries()) +// ============== +// === Config === +// ============== - const config = new Config() - config.updateFromObject(inputConfig) - config.updateFromObject(urlConfig) +class Config { + project: config.Param = new config.Param( + null, + 'Project name to open on startup.' + ) + projectManager: config.Param = new config.Param( + null, + 'An address of the Project Manager service.' + ) + languageServerRpc: config.Param = new config.Param( + null, + 'An address of the Language Server RPC endpoint. This argument should be provided ' + + 'together with `languageServerData` ,`namespace`, and `project` options. They make ' + + 'Enso connect directly to the already spawned Language Server of some project.' + ) + languageServerData: config.Param = new config.Param( + null, + 'An address of the Language Server Data endpoint. This argument should be provided ' + + 'together with `languageServerData` ,`namespace`, and `project` options. They make ' + + 'Enso connect directly to the already spawned Language Server of some project.' + ) + namespace: config.Param = new config.Param( + null, + 'Informs Enso about namespace of the opened project. May be used when connecting to ' + + 'existing Language Server process. Defaults to "local".' + ) + platform: config.Param = new config.Param( + null, + 'The host platform the app is running on. This is used to adjust some UI elements. For ' + + 'example, on macOS, the window close buttons are integrated to the top app panel.' + ) + frame: config.Param = new config.Param( + false, + 'Controls whether a window frame should be visible. Works in native app only.' + ) + darkTheme: config.Param = new config.Param( + false, + 'Controls whether the dark theme should be used. Please note that the dark theme is not ' + + 'fully implemented yet.' + ) + nodeLabels: config.Param = new config.Param( + true, + `Controls whether node labels should be visible.` + ) + dataGathering: config.Param = new config.Param( + true, + 'Controls whether anonymous data gathering should be enabled.' + ) + isInCloud: config.Param = new config.Param( + false, + 'Information if the app is running in the cloud.' + ) + authenticationEnabled: config.Param = new config.Param( + true, + 'Controls whether user authentication is enabled.' + ) + email: config.Param = new config.Param(null, 'The user email, if any.') + applicationConfigUrl: config.Param = new config.Param( + 'https://raw.githubusercontent.com/enso-org/ide/develop/config.json', + 'The application config URL. Used to check for available updates.' + ) + testWorkflow: config.Param = new config.Param( + null, + 'When profiling the application (e.g. with the `./run profile` command), this argument ' + + 'chooses what is profiled.' + ) + skipMinVersionCheck: config.Param = new config.Param( + Version.isDev(), + 'Controls whether the minimum engine version check should be performed. It is set to ' + + '`true` in local builds.' + ) + debug: config.Param = new config.Param( + Version.isDev(), + 'Controls whether the application should be run in the debug mode. In this mode all logs ' + + 'are printed to the console. Otherwise, the logs are hidden unless explicitly shown ' + + 'by calling `showLogs`. Moreover, additional logs from libraries are printed in ' + + 'this mode. The debug mode is set to `true` by default in local builds.' + ) + preferredEngineVersion: config.Param = new config.Param( + Version.ide, + `The preferred engine version.` + ) + enableNewComponentBrowser: config.Param = new config.Param( + true, + 'Controls whether the new component browser should be enabled.' + ) + emitUserTimingMeasurements: config.Param = new config.Param(false, 'TODO') +} - if (await checkMinSupportedVersion(config)) { - if (config.authentication_enabled && !config.entry) { - new FirebaseAuthentication(function (user: any) { - config.email = user.email - runEntryPoint(config) - }) - } else { - await runEntryPoint(config) +// ======================== +// === Main Entry Point === +// ======================== + +class Main { + async main(inputConfig: any) { + const config = Object.assign( + { + pkgWasmUrl: 'assets/pkg-opt.wasm', + pkgJsUrl: 'assets/pkg.js', + shadersUrl: 'assets/shaders', + }, + inputConfig + ) + const appInstance = new app.App({ + config, + configExtension: new Config(), + packageInfo: { + version: BUILD_INFO.default.version, + engineVersion: BUILD_INFO.default.engineVersion, + }, + }) + + if (appInstance.initialized) { + if (appInstance.config.params.dataGathering.value) { + // TODO: Add remote-logging here. + } + if (!(await checkMinSupportedVersion(appInstance.config.params))) { + displayDeprecatedVersionDialog() + } else { + if ( + appInstance.config.params.authenticationEnabled.value && + appInstance.config.params.entry.value != appInstance.config.params.entry.default + ) { + // TODO: authentication here + // appInstance.config.email.value = user.email + appInstance.run() + } else { + appInstance.run() + } + if (appInstance.config.params.email.value) { + logger.log(`User identified as '${appInstance.config.params.email.value}'.`) + } + } } - } else { - // Display a message asking to update the application. - createVersionCheckHtml() } } + +const API = new Main() + +// @ts-ignore +window[globalConfig.windowAppScopeName] = API diff --git a/app/ide-desktop/lib/copy-plugin/src/index.mjs b/app/ide-desktop/lib/copy-plugin/src/index.mjs index 6ec0a272bc..884f1e1586 100644 --- a/app/ide-desktop/lib/copy-plugin/src/index.mjs +++ b/app/ide-desktop/lib/copy-plugin/src/index.mjs @@ -44,8 +44,8 @@ export function create(files_provider) { build.onStart(async () => { console.log('Initial options:', build.initialOptions) + console.log('Collecting files to copy.') files = files_provider() - console.log('Collecting files to copy.', files) }) build.onResolve({ filter: new RegExp(magic) }, async resolve => { console.log('Resolving ', resolve) diff --git a/app/ide-desktop/lib/server/src/index.mjs b/app/ide-desktop/lib/server/src/index.mjs index 1f5b7230e7..23463fb81c 100644 --- a/app/ide-desktop/lib/server/src/index.mjs +++ b/app/ide-desktop/lib/server/src/index.mjs @@ -17,6 +17,8 @@ export async function start({ root, assets, port }) { const freePort = await portfinder.getPortPromise({ port: port ?? DEFAULT_PORT }) + // FIXME: There is an issue probably related with improper caches of served files. Read more + // here: https://github.com/expressjs/serve-static/issues/155 const app = connect() .use(logger('dev', { skip: (req, res) => res.statusCode < 400 })) .use(serveStatic(root)) diff --git a/app/ide-desktop/package-lock.json b/app/ide-desktop/package-lock.json index 7c57da9b1a..f166d43eb9 100644 --- a/app/ide-desktop/package-lock.json +++ b/app/ide-desktop/package-lock.json @@ -10,7 +10,6 @@ "hasInstallScript": true, "workspaces": [ "lib/client", - "lib/common", "lib/content", "lib/copy-plugin", "lib/icons", @@ -42,39 +41,13 @@ "ts-node": "^10.9.1" } }, - "lib/client/node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "lib/common": { - "name": "enso-studio-common", - "version": "1.0.0", - "dependencies": { - "mime-types": "^2.1.35" - } - }, "lib/content": { "name": "enso-studio-content", "version": "1.0.0", "dependencies": { + "@types/mixpanel-browser": "^2.38.0", "@types/semver": "^7.3.9", "enso-gui-server": "^1.0.0", - "enso-studio-common": "1.0.0", - "firebase": "^9.14.0", - "firebaseui": "^6.0.2", "html-loader": "^4.2.0", "mixpanel-browser": "2.45.0" }, @@ -99,9 +72,8 @@ }, "lib/content/node_modules/glob": { "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -116,6 +88,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "lib/content/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "lib/copy-plugin": { "name": "enso-copy-plugin", "version": "1.0.0", @@ -155,6 +139,16 @@ "node": ">=12" } }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -193,31 +187,31 @@ "global-tunnel-ng": "^2.7.1" } }, - "node_modules/@electron/get/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/@electron/get/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "ms": "2.1.2" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@electron/get/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } + "node_modules/@electron/get/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/@electron/remote": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.0.8.tgz", - "integrity": "sha512-P10v3+iFCIvEPeYzTWWGwwHmqWnjoh8RYnbtZAb3RlQefy4guagzIwcWtfftABIfm6JJTNQf4WPSKWZOpLmHXw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.0.9.tgz", + "integrity": "sha512-LR0W0ID6WAKHaSs0x5LX9aiG+5pFBNAJL6eQAJfGkCuZPUa6nZz+czZLdlTDETG45CgF/0raSvCtYOYUpr6c+A==", "peerDependencies": { "electron": ">= 13.0.0" } @@ -238,6 +232,23 @@ "node": ">=8.6" } }, + "node_modules/@electron/universal/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@electron/universal/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -265,6 +276,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/@electron/universal/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/@electron/universal/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -297,9 +314,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.14.tgz", - "integrity": "sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", "cpu": [ "arm" ], @@ -313,9 +330,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz", - "integrity": "sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", "cpu": [ "loong64" ], @@ -328,566 +345,6 @@ "node": ">=12" } }, - "node_modules/@firebase/analytics": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.8.4.tgz", - "integrity": "sha512-Bgr2tMexv0YrL6kjrOF1xVRts8PM6WWmROpfRQjh0xFU4QSoofBJhkVn2NXDXkHWrr5slFfqB5yOnmgAIsHiMw==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/analytics-compat": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.1.17.tgz", - "integrity": "sha512-36ByEDsH6/3YNuD6yig30s2A/+E1pt333r8SJirUE8+aHYl/DGX0PXplKvJWDGamYYjMwet3Kt4XRrB1NY8mLg==", - "dependencies": { - "@firebase/analytics": "0.8.4", - "@firebase/analytics-types": "0.7.1", - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/analytics-types": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.7.1.tgz", - "integrity": "sha512-a1INLjelc1Mqrt2CbGmGdlNBj0zsvwBv0K5q5C6Fje8GSXBMc3+iQQQjzYe/4KkK6nL54UP7ZMeI/Q3VEW72FA==" - }, - "node_modules/@firebase/app": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.8.4.tgz", - "integrity": "sha512-gQntijd+sLaGWjcBQpk33giCEXNzGLB6489NMpypVgEXJwQXYQPSrtb9vUHXot1w1iy/j6xlNl4K8wwwNdRgDg==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-check": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.5.17.tgz", - "integrity": "sha512-P4bm0lbs+VgS7pns322GC0hyKuTDCqYk2X4FGBf133LZaw1NXJpzOteqPdCT0hBCaR0QSHk49gxx+bdnSdd5Fg==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/app-check-compat": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.2.17.tgz", - "integrity": "sha512-yhiAy6U4MuhbY+DCgvG5FcrXkAL+7YohRzqywycQKr31k/ftelbR5l9Zmo2WJMxdLxfubnnqeG/BYCRHlSvk7A==", - "dependencies": { - "@firebase/app-check": "0.5.17", - "@firebase/app-check-types": "0.4.1", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/app-check-interop-types": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.1.tgz", - "integrity": "sha512-QpYh5GmiLA9ob8NWAZpHbNNl9TzxxZI4NLevT6MYPRDXKG9BSmBI7FATRfm5uv2QQUVSQrESKog5CCmU16v+7Q==" - }, - "node_modules/@firebase/app-check-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.4.1.tgz", - "integrity": "sha512-4X79w2X0H5i5qvaho3qkjZg5qdERnKR4gCfy/fxDmdMMP4QgNJHJ9IBk1E+c4cm5HlaZVcLq9K6z8xaRqjZhyw==" - }, - "node_modules/@firebase/app-compat": { - "version": "0.1.39", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.39.tgz", - "integrity": "sha512-F5O/N38dVGFzpe6zM//MslYT80rpX0V+MQNMvONPUlXhvDqS5T+8NMSCWOcZ++Z4Hkj8EvgTJk59AMnD8SdyFw==", - "dependencies": { - "@firebase/app": "0.8.4", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-types": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", - "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" - }, - "node_modules/@firebase/auth": { - "version": "0.20.11", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.20.11.tgz", - "integrity": "sha512-cKy91l4URDG3yWfPK7tjUySh2wCLxtTilsR59jiqQJLReBrQsKP79eFDJ6jqWwbEh3+f1lmoH1nKswwbo9XdmA==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "selenium-webdriver": "4.5.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/auth-compat": { - "version": "0.2.24", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.2.24.tgz", - "integrity": "sha512-IuZQScjtoOLkUHtmIUJ2F3E2OpDOyap6L/9HL/DX3nzEA1LrX7wlpeU6OF2jS9E0KLueWKIrSkIQOOsKoQj/sA==", - "dependencies": { - "@firebase/auth": "0.20.11", - "@firebase/auth-types": "0.11.1", - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "selenium-webdriver": "4.5.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", - "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/auth-types": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.1.tgz", - "integrity": "sha512-ud7T39VG9ptTrC2fOy/XlU+ubC+BVuBJPteuzsPZSa9l7gkntvWgVb3Z/3FxqqRPlkVUYiyvmsbRN3DE1He2ow==", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/component": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", - "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", - "dependencies": { - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", - "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", - "dependencies": { - "@firebase/auth-interop-types": "0.1.7", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", - "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/database": "0.13.10", - "@firebase/database-types": "0.9.17", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-types": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", - "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", - "dependencies": { - "@firebase/app-types": "0.8.1", - "@firebase/util": "1.7.3" - } - }, - "node_modules/@firebase/firestore": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.7.3.tgz", - "integrity": "sha512-hnA8hljwJBpejv0SPlt0yiej1wz3VRcLzoNAZujTCI1wLoADkRNsqic5uN/Ge0M0vbmHliLXtet/PDqvEbB9Ww==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "@firebase/webchannel-wrapper": "0.8.1", - "@grpc/grpc-js": "^1.3.2", - "@grpc/proto-loader": "^0.6.13", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=10.10.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/firestore-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.2.3.tgz", - "integrity": "sha512-FgJwGCA2K+lsGk6gbJo57qn4iocQSGfOlNi2s4QsEO/WOVIU00yYGm408fN7iAGpr9d5VKyulO4sYcic7cS51g==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/firestore": "3.7.3", - "@firebase/firestore-types": "2.5.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/firestore-types": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", - "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/functions": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.8.8.tgz", - "integrity": "sha512-weNcDQJcH3/2YFaXd5dF5pUk3IQdZY60QNuWpq7yS+uaPlCRHjT0K989Q3ZcmYwXz7mHTfhlQamXdA4Yobgt+Q==", - "dependencies": { - "@firebase/app-check-interop-types": "0.1.1", - "@firebase/auth-interop-types": "0.1.7", - "@firebase/component": "0.5.21", - "@firebase/messaging-interop-types": "0.1.1", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/functions-compat": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.2.8.tgz", - "integrity": "sha512-5w668whT+bm6oVcFqIxfFbn9N77WycpNCfZNg1l0iC+5RLSt53RTVu43pqi43vh23Vp4ad+SRBgZiQGAMen5wA==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/functions": "0.8.8", - "@firebase/functions-types": "0.5.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/functions-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.5.1.tgz", - "integrity": "sha512-olEJnTuULM/ws0pwhHA0Ze5oIdpFbZsdBGCaBhyL4pm1NUR4Moh0cyAsqr+VtqHCNMGquHU1GJ77qITkoonp0w==" - }, - "node_modules/@firebase/installations": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.5.16.tgz", - "integrity": "sha512-k3iyjr+yZnDOcJbP+CCZW3/zQJf9gYL2CNBJs9QbmFJoLz7cgIcnAT/XNDMudxcggF1goLfq4+MygpzHD0NzLA==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/installations-compat": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.1.16.tgz", - "integrity": "sha512-Xp7s3iUMZ6/TN0a+g1kpHNEn7h59kSxi44/2I7bd3X6xwHnxMu0TqYB7U9WfqEhqiI9iKulL3g06wIZqaklElw==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/installations-types": "0.4.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/installations-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.4.1.tgz", - "integrity": "sha512-ac906QcmipomZjSasGDYNS1LDy4JNGzQ4VXHpFtoOrI6U2QGFkRezZpI+5bzfU062JOD+doO6irYC6Uwnv/GnA==", - "peerDependencies": { - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/logger": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", - "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/messaging": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.11.0.tgz", - "integrity": "sha512-V7+Xw4QlB8PgINY7Wml+Uj8A3S2nR0ooVoaqfRJ8ZN3W7A4aO/DCkjPsf6DXehwfqRLA7PGB9Boe8l9Idy7icA==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/messaging-interop-types": "0.1.1", - "@firebase/util": "1.7.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/messaging-compat": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.1.21.tgz", - "integrity": "sha512-oxQCQ8EXqpSaTybryokbEM/LAqkG0L7OJuucllCg5roqRGIHE437Abus0Bn67P8TKJaYjyKxomg8wCvfmInjlg==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/messaging": "0.11.0", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/messaging-interop-types": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.1.1.tgz", - "integrity": "sha512-7XuY87zPh01EBaeS3s6co31Il5oGbPl5MxAg6Uj3fPv7PqJQlbwQ+B5k7CKSF/Y26tRxp+u+usxIvIWCSEA8CQ==" - }, - "node_modules/@firebase/performance": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.5.17.tgz", - "integrity": "sha512-NDgzI5JYo6Itnj1FWhMkK3LtwKhtOnhC+WBkxezjzFVuCOornQjvu7ucAU1o2dHXh7MFruhHGFPsHyfkkMCljA==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/performance-compat": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.1.17.tgz", - "integrity": "sha512-Hci5MrDlRuqwVozq7LaSAufXXElz+AtmEQArix64kLRJqHhOu5K/8TpuZXM/klR6gnLyIrk+01CrAemH3zHpDw==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/performance": "0.5.17", - "@firebase/performance-types": "0.1.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/performance-types": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.1.1.tgz", - "integrity": "sha512-wiJRLBg8EPaYSGJqx7aqkZ3L5fULfZa9zOTs4C06K020g0zzJh9kUUO/0U3wvHz7zRQjJxTO8Jw4SDjxs3EZrA==" - }, - "node_modules/@firebase/remote-config": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.3.15.tgz", - "integrity": "sha512-ZCyqoCaftoNvc2r4zPaqNV4OgC4sRHjcQI+agzXESnhDLnTY8DpCaQ0m9j6deHuxxDOgu8QPDb8psLbjR+9CgQ==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/remote-config-compat": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.1.16.tgz", - "integrity": "sha512-BWonzeqODnGki/fZ17zOnjJFR5CWbIOU0PmYGjWBnbkWxpFDdE3zNsz8JTVd/Mkt7y2PHFMYpLsyZ473E/62FQ==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/remote-config": "0.3.15", - "@firebase/remote-config-types": "0.2.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/remote-config-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.2.1.tgz", - "integrity": "sha512-1PGx4vKtMMd5uB6G1Nj2b8fOnJx7mIJGzkdyfhIM1oQx9k3dJ+pVu4StrNm46vHaD8ZlOQLr91YfUE43xSXwSg==" - }, - "node_modules/@firebase/storage": { - "version": "0.9.14", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.9.14.tgz", - "integrity": "sha512-he8VAJ4BLkQdebnna15TI1/ymkwQTeKnjA/psKMAJ2+/UswD/68bCMKOlTrMvw6Flv3zc5YZk1xdL9DHR0i6wg==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/storage-compat": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.1.22.tgz", - "integrity": "sha512-uv33WnAEcxf2983Z03uhJmKc91LKSsRijFwut8xeoJamJoGAVj1Tc9Mio491aI1KZ+RMkNFghHL2FpxjuvxpPg==", - "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/storage": "0.9.14", - "@firebase/storage-types": "0.6.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/storage-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.1.tgz", - "integrity": "sha512-/pkNzKiGCSjdBBZHPvWL1kkPZfM3pFJ38HPJE1xTHwLBwdrFb4JrmY+5/E4ma5ePsbejecIOD1SZhEKDB/JwUQ==", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/util": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", - "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/webchannel-wrapper": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.8.1.tgz", - "integrity": "sha512-CJW8vxt6bJaBeco2VnlJjmCmAkrrtIdf0GGKvpAB4J5gw8Gi0rHb+qsgKp6LsyS5W6ALPLawLs7phZmw02dvLw==" - }, - "node_modules/@grpc/grpc-js": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", - "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", - "dependencies": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", - "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@grpc/grpc-js/node_modules/protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, - "node_modules/@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.11.3", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -932,12 +389,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/@malept/cross-spawn-promise": { @@ -962,65 +419,6 @@ "node": ">= 10" } }, - "node_modules/@malept/cross-spawn-promise/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@malept/cross-spawn-promise/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@malept/cross-spawn-promise/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@malept/cross-spawn-promise/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@malept/cross-spawn-promise/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@malept/flatpak-bundler": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", @@ -1036,6 +434,23 @@ "node": ">= 10.0.0" } }, + "node_modules/@malept/flatpak-bundler/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -1063,6 +478,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/@malept/flatpak-bundler/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/@malept/flatpak-bundler/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -1107,60 +528,6 @@ "node": ">= 8" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -1274,11 +641,6 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "peer": true }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -1286,6 +648,11 @@ "dev": true, "optional": true }, + "node_modules/@types/mixpanel-browser": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.38.0.tgz", + "integrity": "sha512-TR8rvsILnqXA7oiiGOxuMGXwvDeCoQDonXJB5UR+TYvEAFpiK8ReFj5LhZT+Xhm3NpI9aPoju30jB2ssorSUww==" + }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -1293,9 +660,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" }, "node_modules/@types/plist": { "version": "3.0.2", @@ -1321,9 +688,9 @@ "optional": true }, "node_modules/@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", + "integrity": "sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -1561,6 +928,29 @@ "node": ">= 6.0.0" } }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1657,23 +1047,21 @@ "node": ">=14.0.0" } }, - "node_modules/app-builder-lib/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/app-builder-lib/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/app-builder-lib/node_modules/ci-info": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.6.1.tgz", - "integrity": "sha512-up5ggbaDqOqJ4UqLKZ2naVkyqSJQgJi5lwD6b6mM748ysrghDBX0bx/qJTUHzw7zu6Mq4gycviSF5hJnwceD8w==", - "dev": true, + "ms": "2.1.2" + }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/app-builder-lib/node_modules/fs-extra": { @@ -1690,18 +1078,6 @@ "node": ">=12" } }, - "node_modules/app-builder-lib/node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/app-builder-lib/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -1714,17 +1090,11 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/app-builder-lib/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } + "node_modules/app-builder-lib/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/app-builder-lib/node_modules/semver": { "version": "7.3.8", @@ -1793,28 +1163,6 @@ "@types/glob": "^7.1.1" } }, - "node_modules/asar/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/asar/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -1883,15 +1231,16 @@ } }, "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -1962,20 +1311,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -2192,27 +1527,44 @@ "node": ">=12.0.0" } }, - "node_modules/builder-util/node_modules/ci-info": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.6.1.tgz", - "integrity": "sha512-up5ggbaDqOqJ4UqLKZ2naVkyqSJQgJi5lwD6b6mM748ysrghDBX0bx/qJTUHzw7zu6Mq4gycviSF5hJnwceD8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/builder-util/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/builder-util-runtime/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">= 8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/builder-util-runtime/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/builder-util/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/builder-util/node_modules/fs-extra": { @@ -2229,18 +1581,6 @@ "node": ">=12" } }, - "node_modules/builder-util/node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/builder-util/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -2253,35 +1593,11 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/builder-util/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/builder-util/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/builder-util/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/builder-util/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/builder-util/node_modules/universalify": { "version": "2.0.0", @@ -2292,21 +1608,6 @@ "node": ">= 10.0.0" } }, - "node_modules/builder-util/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -2324,20 +1625,6 @@ "node": ">=8" } }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cacheable-request/node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -2368,9 +1655,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001431", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", - "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", + "version": "1.0.30001442", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", + "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==", "funding": [ { "type": "opencollective", @@ -2427,10 +1714,19 @@ "dev": true }, "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } }, "node_modules/clean-css": { "version": "5.2.0", @@ -2577,7 +1873,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/config-chain": { "version": "1.1.13", @@ -2620,28 +1917,16 @@ "node": ">= 0.10.0" } }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/connected": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/connected/-/connected-0.0.2.tgz", "integrity": "sha512-J8DB7618GkIYjc1RCxSdG3vffhhYRwHNEckjOGfwAbabQIMgKsL5c54IaWtisulEwoOEbEODEUak4kyJP2GJ/Q==" }, "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true }, "node_modules/crc": { "version": "3.8.0", @@ -2670,19 +1955,17 @@ } }, "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=4.8" + "node": ">= 8" } }, "node_modules/crypto-js": { @@ -2713,19 +1996,11 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "ms": "2.0.0" } }, "node_modules/decompress-response": { @@ -2810,11 +2085,6 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "optional": true }, - "node_modules/dialog-polyfill": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.4.10.tgz", - "integrity": "sha512-j5yGMkP8T00UFgyO+78OxiN5vC5dzRQF3BEio+LhNvDbyfxWBsi3sfPArDm54VloaJwy2hm3erEiDWqHRC8rzw==" - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3072,12 +2342,6 @@ "node": ">=14.0.0" } }, - "node_modules/electron-builder/node_modules/ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", - "dev": true - }, "node_modules/electron-builder/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -3092,18 +2356,6 @@ "node": ">=12" } }, - "node_modules/electron-builder/node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/electron-builder/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -3125,24 +2377,6 @@ "node": ">= 10.0.0" } }, - "node_modules/electron-builder/node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/electron-is-dev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-2.0.0.tgz", @@ -3165,6 +2399,23 @@ "node": ">= 10.0.0" } }, + "node_modules/electron-notarize/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/electron-notarize/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -3192,6 +2443,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/electron-notarize/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/electron-notarize/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -3223,15 +2480,6 @@ "node": ">=4.0.0" } }, - "node_modules/electron-osx-sign/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/electron-osx-sign/node_modules/isbinaryfile": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", @@ -3244,12 +2492,6 @@ "node": ">=0.6.0" } }, - "node_modules/electron-osx-sign/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, "node_modules/electron-publish": { "version": "22.14.13", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.14.13.tgz", @@ -3307,9 +2549,9 @@ "peer": true }, "node_modules/electron/node_modules/@types/node": { - "version": "16.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz", - "integrity": "sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==" + "version": "16.18.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", + "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -3342,9 +2584,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -3365,10 +2607,6 @@ "resolved": "lib/server", "link": true }, - "node_modules/enso-studio-common": { - "resolved": "lib/common", - "link": true - }, "node_modules/enso-studio-content": { "resolved": "lib/content", "link": true @@ -3423,9 +2661,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.14.tgz", - "integrity": "sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", "dev": true, "hasInstallScript": true, "bin": { @@ -3435,34 +2673,34 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.15.14", - "@esbuild/linux-loong64": "0.15.14", - "esbuild-android-64": "0.15.14", - "esbuild-android-arm64": "0.15.14", - "esbuild-darwin-64": "0.15.14", - "esbuild-darwin-arm64": "0.15.14", - "esbuild-freebsd-64": "0.15.14", - "esbuild-freebsd-arm64": "0.15.14", - "esbuild-linux-32": "0.15.14", - "esbuild-linux-64": "0.15.14", - "esbuild-linux-arm": "0.15.14", - "esbuild-linux-arm64": "0.15.14", - "esbuild-linux-mips64le": "0.15.14", - "esbuild-linux-ppc64le": "0.15.14", - "esbuild-linux-riscv64": "0.15.14", - "esbuild-linux-s390x": "0.15.14", - "esbuild-netbsd-64": "0.15.14", - "esbuild-openbsd-64": "0.15.14", - "esbuild-sunos-64": "0.15.14", - "esbuild-windows-32": "0.15.14", - "esbuild-windows-64": "0.15.14", - "esbuild-windows-arm64": "0.15.14" + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" } }, "node_modules/esbuild-android-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz", - "integrity": "sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", "cpu": [ "x64" ], @@ -3476,9 +2714,9 @@ } }, "node_modules/esbuild-android-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz", - "integrity": "sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", "cpu": [ "arm64" ], @@ -3498,9 +2736,9 @@ "dev": true }, "node_modules/esbuild-darwin-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz", - "integrity": "sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "cpu": [ "x64" ], @@ -3514,9 +2752,9 @@ } }, "node_modules/esbuild-darwin-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz", - "integrity": "sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", "cpu": [ "arm64" ], @@ -3664,9 +2902,9 @@ ] }, "node_modules/esbuild-freebsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz", - "integrity": "sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", "cpu": [ "x64" ], @@ -3680,9 +2918,9 @@ } }, "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz", - "integrity": "sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", "cpu": [ "arm64" ], @@ -3696,9 +2934,9 @@ } }, "node_modules/esbuild-linux-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz", - "integrity": "sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", "cpu": [ "ia32" ], @@ -3712,9 +2950,9 @@ } }, "node_modules/esbuild-linux-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz", - "integrity": "sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", "cpu": [ "x64" ], @@ -3728,9 +2966,9 @@ } }, "node_modules/esbuild-linux-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz", - "integrity": "sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", "cpu": [ "arm" ], @@ -3744,9 +2982,9 @@ } }, "node_modules/esbuild-linux-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz", - "integrity": "sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", "cpu": [ "arm64" ], @@ -3760,9 +2998,9 @@ } }, "node_modules/esbuild-linux-mips64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz", - "integrity": "sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", "cpu": [ "mips64el" ], @@ -3776,9 +3014,9 @@ } }, "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz", - "integrity": "sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", "cpu": [ "ppc64" ], @@ -3792,9 +3030,9 @@ } }, "node_modules/esbuild-linux-riscv64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz", - "integrity": "sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", "cpu": [ "riscv64" ], @@ -3808,9 +3046,9 @@ } }, "node_modules/esbuild-linux-s390x": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz", - "integrity": "sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", "cpu": [ "s390x" ], @@ -3824,9 +3062,9 @@ } }, "node_modules/esbuild-netbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz", - "integrity": "sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", "cpu": [ "x64" ], @@ -3840,9 +3078,9 @@ } }, "node_modules/esbuild-openbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz", - "integrity": "sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", "cpu": [ "x64" ], @@ -3917,9 +3155,9 @@ } }, "node_modules/esbuild-sunos-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz", - "integrity": "sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", "cpu": [ "x64" ], @@ -3933,9 +3171,9 @@ } }, "node_modules/esbuild-windows-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz", - "integrity": "sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", "cpu": [ "ia32" ], @@ -3949,9 +3187,9 @@ } }, "node_modules/esbuild-windows-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz", - "integrity": "sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", "cpu": [ "x64" ], @@ -3965,9 +3203,9 @@ } }, "node_modules/esbuild-windows-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz", - "integrity": "sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", "cpu": [ "arm64" ], @@ -4120,29 +3358,35 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/extract-zip/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "pump": "^3.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">=8" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true, "engines": [ "node >=0.6.0" - ], - "optional": true + ] }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -4171,25 +3415,14 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -4216,6 +3449,18 @@ "minimatch": "^5.0.1" } }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4245,19 +3490,6 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/find-yarn-workspace-root": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", @@ -4267,51 +3499,6 @@ "micromatch": "^4.0.2" } }, - "node_modules/firebase": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.14.0.tgz", - "integrity": "sha512-wePrsf7W33mhT7RVXQavragoAgXb/NDm22vuhwJXkprrQ2Y9alrEKC5LTAtLJL3P2dHdDmeylS6PLZwWPEE79A==", - "dependencies": { - "@firebase/analytics": "0.8.4", - "@firebase/analytics-compat": "0.1.17", - "@firebase/app": "0.8.4", - "@firebase/app-check": "0.5.17", - "@firebase/app-check-compat": "0.2.17", - "@firebase/app-compat": "0.1.39", - "@firebase/app-types": "0.8.1", - "@firebase/auth": "0.20.11", - "@firebase/auth-compat": "0.2.24", - "@firebase/database": "0.13.10", - "@firebase/database-compat": "0.2.10", - "@firebase/firestore": "3.7.3", - "@firebase/firestore-compat": "0.2.3", - "@firebase/functions": "0.8.8", - "@firebase/functions-compat": "0.2.8", - "@firebase/installations": "0.5.16", - "@firebase/installations-compat": "0.1.16", - "@firebase/messaging": "0.11.0", - "@firebase/messaging-compat": "0.1.21", - "@firebase/performance": "0.5.17", - "@firebase/performance-compat": "0.1.17", - "@firebase/remote-config": "0.3.15", - "@firebase/remote-config-compat": "0.1.16", - "@firebase/storage": "0.9.14", - "@firebase/storage-compat": "0.1.22", - "@firebase/util": "1.7.3" - } - }, - "node_modules/firebaseui": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/firebaseui/-/firebaseui-6.0.2.tgz", - "integrity": "sha512-Jwwn2I657loKrvedeCrwED9UibLFl8Cm0uH2ntDBSCpruWzG4HXlIWb35WsDdXMILRPQjJ1PwVwuRsrnsxcaXA==", - "dependencies": { - "dialog-polyfill": "^0.4.7", - "material-design-lite": "^1.2.0" - }, - "peerDependencies": { - "firebase": "^9.1.3" - } - }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -4350,12 +3537,11 @@ "dev": true }, "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dependencies": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" }, @@ -4366,7 +3552,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/function-bind": { "version": "1.1.1", @@ -4397,14 +3584,17 @@ } }, "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dependencies": { "pump": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/getpass": { @@ -4426,6 +3616,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4459,26 +3650,6 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "peer": true }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/global": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", @@ -4522,9 +3693,9 @@ } }, "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, "dependencies": { "ini": "2.0.0" @@ -4596,6 +3767,17 @@ "node": ">=8.6" } }, + "node_modules/got/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -4715,17 +3897,17 @@ } }, "node_modules/html-minifier-terser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.0.0.tgz", - "integrity": "sha512-Adqk0b/pWKIQiGvEAuzPKpBKNHiwblr3QSGS7TTr6v+xXKV9AI2k4vWW+6Oytt6Z5SeBnfvYypKOnz8r75pz3Q==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.1.0.tgz", + "integrity": "sha512-BvPO2S7Ip0Q5qt+Y8j/27Vclj6uHC6av0TMoDn7/bJPhMWHI2UtR2e/zEgJn3/qYAmxumrGp9q4UHurL6mtW9Q==", "dependencies": { "camel-case": "^4.1.2", "clean-css": "5.2.0", - "commander": "^9.4.0", - "entities": "^4.3.1", + "commander": "^9.4.1", + "entities": "^4.4.0", "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^5.14.2" + "terser": "^5.15.1" }, "bin": { "html-minifier-terser": "cli.js" @@ -4735,9 +3917,9 @@ } }, "node_modules/html-minifier-terser/node_modules/commander": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", - "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "engines": { "node": "^12.20.0 || >=14" } @@ -4770,11 +3952,6 @@ "node": ">= 0.8" } }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -4789,6 +3966,29 @@ "node": ">= 6" } }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -4817,6 +4017,29 @@ "node": ">= 6" } }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -4846,11 +4069,6 @@ "node": ">=0.10.0" } }, - "node_modules/idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4883,11 +4101,6 @@ "node": ">=0.10.0" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, "node_modules/import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -4910,6 +4123,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -4942,12 +4156,12 @@ "dev": true }, "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, "dependencies": { - "ci-info": "^2.0.0" + "ci-info": "^3.2.0" }, "bin": { "is-ci": "bin.js" @@ -5082,11 +4296,6 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -5129,28 +4338,6 @@ "node": ">=10" } }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jake/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/javascript-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", @@ -5306,9 +4493,9 @@ "devOptional": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -5340,21 +4527,6 @@ "node": ">=0.6.0" } }, - "node_modules/jsprim/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "node_modules/jsprim/node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, "node_modules/jsprim/node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -5369,17 +4541,6 @@ "extsprintf": "^1.2.0" } }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, "node_modules/keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -5415,14 +4576,6 @@ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", "dev": true }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/load-bmfont": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", @@ -5470,9 +4623,9 @@ } }, "node_modules/loader-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", - "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "dependencies": { "big.js": "^5.2.2", @@ -5488,16 +4641,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -5550,15 +4693,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5577,14 +4711,6 @@ "node": ">=10" } }, - "node_modules/material-design-lite": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/material-design-lite/-/material-design-lite-1.3.0.tgz", - "integrity": "sha512-ao76b0bqSTKcEMt7Pui+J/S3eVF0b3GWfuKUwfe2lP5DKlLZOwBq37e0/bXEzxrw7/SuHAuYAdoCwY6mAYhrsg==", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5662,15 +4788,25 @@ } }, "node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/minimist": { @@ -5718,24 +4854,11 @@ "node": ">= 0.8.0" } }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/ms": { + "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -5764,9 +4887,9 @@ } }, "node_modules/node-abi": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.28.0.tgz", - "integrity": "sha512-fRlDb4I0eLcQeUvGq7IY3xHrSb0c9ummdvDSYWfT9+LKP+3jCKw/tKoqaM7r1BAoiAC6GtwyjaGnOz6B3OtF+A==", + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz", + "integrity": "sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -5797,29 +4920,10 @@ "dev": true, "optional": true }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", + "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", "peer": true }, "node_modules/normalize-url": { @@ -5944,20 +5048,6 @@ "node": ">=8" } }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -6008,9 +5098,9 @@ } }, "node_modules/parse5": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.1.tgz", - "integrity": "sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dependencies": { "entities": "^4.4.0" }, @@ -6036,16 +5126,16 @@ } }, "node_modules/patch-package": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.0.tgz", - "integrity": "sha512-tC3EqJmo74yKqfsMzELaFwxOAu6FH6t+FzFOsnWAuARm7/n2xB5AOeOueE221eM9gtMuIKMKpF9tBy/X2mNP0Q==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", + "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", "dev": true, "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", "cross-spawn": "^6.0.5", "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^7.0.1", + "fs-extra": "^9.0.0", "is-ci": "^2.0.0", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", @@ -6064,15 +5154,68 @@ "npm": ">5" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/patch-package/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/patch-package/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, "engines": { - "node": ">=0.10.0" + "node": ">=4.8" } }, - "node_modules/path-key": { + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/patch-package/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/patch-package/node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", @@ -6081,6 +5224,75 @@ "node": ">=4" } }, + "node_modules/patch-package/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/patch-package/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/patch-package/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/patch-package/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/patch-package/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -6209,6 +5421,11 @@ "ms": "^2.1.1" } }, + "node_modules/portfinder/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -6252,11 +5469,6 @@ "node": ">= 0.6.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -6271,31 +5483,6 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "optional": true }, - "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -6418,17 +5605,17 @@ } }, "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/registry-auth-token": { @@ -6691,51 +5878,12 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/selenium-webdriver": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.5.0.tgz", - "integrity": "sha512-9mSFii+lRwcnT2KUAB1kqvx6+mMiiQHH60Y0VUtr3kxxi3oZ3CV3B8e2nuJ7T4SPb+Q6VA0swswe7rYpez07Bg==", - "dependencies": { - "jszip": "^3.10.0", - "tmp": "^0.2.1", - "ws": ">=8.7.0" - }, - "engines": { - "node": ">= 14.20.0" - } - }, - "node_modules/selenium-webdriver/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/selenium-webdriver/node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" } }, "node_modules/semver-compare": { @@ -6756,15 +5904,6 @@ "node": ">=8" } }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -6788,19 +5927,6 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -6874,20 +6000,15 @@ "node": ">= 0.8.0" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/sharp": { - "version": "0.31.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.2.tgz", - "integrity": "sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==", + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz", + "integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -6929,24 +6050,24 @@ } }, "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "dependencies": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/signal-exit": { @@ -7122,6 +6243,7 @@ "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, "node_modules/sprintf-js": { @@ -7194,13 +6316,34 @@ } }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -7245,6 +6388,27 @@ "node": ">= 8.0" } }, + "node_modules/sumchecker/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/sumchecker/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7293,20 +6457,6 @@ "node": ">=6" } }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", @@ -7353,9 +6503,9 @@ } }, "node_modules/terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", + "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", @@ -7403,29 +6553,16 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", - "dev": true, - "engines": { - "node": "*" - } + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", + "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==", + "dev": true }, "node_modules/tmp": { "version": "0.0.33", @@ -7532,11 +6669,6 @@ "node": ">=0.8" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -7547,9 +6679,9 @@ } }, "node_modules/ts-loader": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz", - "integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -7677,9 +6809,9 @@ } }, "node_modules/typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -7771,6 +6903,24 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, + "node_modules/update-notifier/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/update-notifier/node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, "node_modules/update-notifier/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -7826,7 +6976,8 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "node_modules/utils-merge": { "version": "1.0.1", @@ -7867,13 +7018,6 @@ "node": ">=0.6.0" } }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "optional": true - }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -7887,15 +7031,10 @@ "node": ">=10.13.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, "node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -7948,46 +7087,19 @@ "node": ">=10.13.0" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { "isexe": "^2.0.0" }, "bin": { - "which": "bin/which" + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, "node_modules/widest-line": { @@ -8036,15 +7148,15 @@ } }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -8160,29 +7272,29 @@ } }, "node_modules/yaml-loader/node_modules/yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", "dev": true, "engines": { "node": ">= 14" } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -8193,24 +7305,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -8238,6 +7332,18 @@ "dev": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } } }, "@develar/schema-utils": { @@ -8266,27 +7372,25 @@ "sumchecker": "^3.0.1" }, "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "ms": "2.1.2" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, "@electron/remote": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.0.8.tgz", - "integrity": "sha512-P10v3+iFCIvEPeYzTWWGwwHmqWnjoh8RYnbtZAb3RlQefy4guagzIwcWtfftABIfm6JJTNQf4WPSKWZOpLmHXw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.0.9.tgz", + "integrity": "sha512-LR0W0ID6WAKHaSs0x5LX9aiG+5pFBNAJL6eQAJfGkCuZPUa6nZz+czZLdlTDETG45CgF/0raSvCtYOYUpr6c+A==", "requires": {} }, "@electron/universal": { @@ -8302,6 +7406,15 @@ "fs-extra": "^9.0.1" }, "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -8324,6 +7437,12 @@ "universalify": "^2.0.0" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -8350,487 +7469,19 @@ } }, "@esbuild/android-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.14.tgz", - "integrity": "sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz", - "integrity": "sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", "dev": true, "optional": true }, - "@firebase/analytics": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.8.4.tgz", - "integrity": "sha512-Bgr2tMexv0YrL6kjrOF1xVRts8PM6WWmROpfRQjh0xFU4QSoofBJhkVn2NXDXkHWrr5slFfqB5yOnmgAIsHiMw==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/analytics-compat": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.1.17.tgz", - "integrity": "sha512-36ByEDsH6/3YNuD6yig30s2A/+E1pt333r8SJirUE8+aHYl/DGX0PXplKvJWDGamYYjMwet3Kt4XRrB1NY8mLg==", - "requires": { - "@firebase/analytics": "0.8.4", - "@firebase/analytics-types": "0.7.1", - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/analytics-types": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.7.1.tgz", - "integrity": "sha512-a1INLjelc1Mqrt2CbGmGdlNBj0zsvwBv0K5q5C6Fje8GSXBMc3+iQQQjzYe/4KkK6nL54UP7ZMeI/Q3VEW72FA==" - }, - "@firebase/app": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.8.4.tgz", - "integrity": "sha512-gQntijd+sLaGWjcBQpk33giCEXNzGLB6489NMpypVgEXJwQXYQPSrtb9vUHXot1w1iy/j6xlNl4K8wwwNdRgDg==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - } - }, - "@firebase/app-check": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.5.17.tgz", - "integrity": "sha512-P4bm0lbs+VgS7pns322GC0hyKuTDCqYk2X4FGBf133LZaw1NXJpzOteqPdCT0hBCaR0QSHk49gxx+bdnSdd5Fg==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/app-check-compat": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.2.17.tgz", - "integrity": "sha512-yhiAy6U4MuhbY+DCgvG5FcrXkAL+7YohRzqywycQKr31k/ftelbR5l9Zmo2WJMxdLxfubnnqeG/BYCRHlSvk7A==", - "requires": { - "@firebase/app-check": "0.5.17", - "@firebase/app-check-types": "0.4.1", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/app-check-interop-types": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.1.tgz", - "integrity": "sha512-QpYh5GmiLA9ob8NWAZpHbNNl9TzxxZI4NLevT6MYPRDXKG9BSmBI7FATRfm5uv2QQUVSQrESKog5CCmU16v+7Q==" - }, - "@firebase/app-check-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.4.1.tgz", - "integrity": "sha512-4X79w2X0H5i5qvaho3qkjZg5qdERnKR4gCfy/fxDmdMMP4QgNJHJ9IBk1E+c4cm5HlaZVcLq9K6z8xaRqjZhyw==" - }, - "@firebase/app-compat": { - "version": "0.1.39", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.39.tgz", - "integrity": "sha512-F5O/N38dVGFzpe6zM//MslYT80rpX0V+MQNMvONPUlXhvDqS5T+8NMSCWOcZ++Z4Hkj8EvgTJk59AMnD8SdyFw==", - "requires": { - "@firebase/app": "0.8.4", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/app-types": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", - "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" - }, - "@firebase/auth": { - "version": "0.20.11", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.20.11.tgz", - "integrity": "sha512-cKy91l4URDG3yWfPK7tjUySh2wCLxtTilsR59jiqQJLReBrQsKP79eFDJ6jqWwbEh3+f1lmoH1nKswwbo9XdmA==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "selenium-webdriver": "4.5.0", - "tslib": "^2.1.0" - } - }, - "@firebase/auth-compat": { - "version": "0.2.24", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.2.24.tgz", - "integrity": "sha512-IuZQScjtoOLkUHtmIUJ2F3E2OpDOyap6L/9HL/DX3nzEA1LrX7wlpeU6OF2jS9E0KLueWKIrSkIQOOsKoQj/sA==", - "requires": { - "@firebase/auth": "0.20.11", - "@firebase/auth-types": "0.11.1", - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "selenium-webdriver": "4.5.0", - "tslib": "^2.1.0" - } - }, - "@firebase/auth-interop-types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", - "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", - "requires": {} - }, - "@firebase/auth-types": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.1.tgz", - "integrity": "sha512-ud7T39VG9ptTrC2fOy/XlU+ubC+BVuBJPteuzsPZSa9l7gkntvWgVb3Z/3FxqqRPlkVUYiyvmsbRN3DE1He2ow==", - "requires": {} - }, - "@firebase/component": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", - "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", - "requires": { - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/database": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", - "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", - "requires": { - "@firebase/auth-interop-types": "0.1.7", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "@firebase/database-compat": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", - "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/database": "0.13.10", - "@firebase/database-types": "0.9.17", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/database-types": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", - "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", - "requires": { - "@firebase/app-types": "0.8.1", - "@firebase/util": "1.7.3" - } - }, - "@firebase/firestore": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.7.3.tgz", - "integrity": "sha512-hnA8hljwJBpejv0SPlt0yiej1wz3VRcLzoNAZujTCI1wLoADkRNsqic5uN/Ge0M0vbmHliLXtet/PDqvEbB9Ww==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "@firebase/webchannel-wrapper": "0.8.1", - "@grpc/grpc-js": "^1.3.2", - "@grpc/proto-loader": "^0.6.13", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - } - }, - "@firebase/firestore-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.2.3.tgz", - "integrity": "sha512-FgJwGCA2K+lsGk6gbJo57qn4iocQSGfOlNi2s4QsEO/WOVIU00yYGm408fN7iAGpr9d5VKyulO4sYcic7cS51g==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/firestore": "3.7.3", - "@firebase/firestore-types": "2.5.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/firestore-types": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", - "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", - "requires": {} - }, - "@firebase/functions": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.8.8.tgz", - "integrity": "sha512-weNcDQJcH3/2YFaXd5dF5pUk3IQdZY60QNuWpq7yS+uaPlCRHjT0K989Q3ZcmYwXz7mHTfhlQamXdA4Yobgt+Q==", - "requires": { - "@firebase/app-check-interop-types": "0.1.1", - "@firebase/auth-interop-types": "0.1.7", - "@firebase/component": "0.5.21", - "@firebase/messaging-interop-types": "0.1.1", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - } - }, - "@firebase/functions-compat": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.2.8.tgz", - "integrity": "sha512-5w668whT+bm6oVcFqIxfFbn9N77WycpNCfZNg1l0iC+5RLSt53RTVu43pqi43vh23Vp4ad+SRBgZiQGAMen5wA==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/functions": "0.8.8", - "@firebase/functions-types": "0.5.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/functions-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.5.1.tgz", - "integrity": "sha512-olEJnTuULM/ws0pwhHA0Ze5oIdpFbZsdBGCaBhyL4pm1NUR4Moh0cyAsqr+VtqHCNMGquHU1GJ77qITkoonp0w==" - }, - "@firebase/installations": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.5.16.tgz", - "integrity": "sha512-k3iyjr+yZnDOcJbP+CCZW3/zQJf9gYL2CNBJs9QbmFJoLz7cgIcnAT/XNDMudxcggF1goLfq4+MygpzHD0NzLA==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - } - }, - "@firebase/installations-compat": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.1.16.tgz", - "integrity": "sha512-Xp7s3iUMZ6/TN0a+g1kpHNEn7h59kSxi44/2I7bd3X6xwHnxMu0TqYB7U9WfqEhqiI9iKulL3g06wIZqaklElw==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/installations-types": "0.4.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/installations-types": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.4.1.tgz", - "integrity": "sha512-ac906QcmipomZjSasGDYNS1LDy4JNGzQ4VXHpFtoOrI6U2QGFkRezZpI+5bzfU062JOD+doO6irYC6Uwnv/GnA==", - "requires": {} - }, - "@firebase/logger": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", - "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/messaging": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.11.0.tgz", - "integrity": "sha512-V7+Xw4QlB8PgINY7Wml+Uj8A3S2nR0ooVoaqfRJ8ZN3W7A4aO/DCkjPsf6DXehwfqRLA7PGB9Boe8l9Idy7icA==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/messaging-interop-types": "0.1.1", - "@firebase/util": "1.7.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - } - }, - "@firebase/messaging-compat": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.1.21.tgz", - "integrity": "sha512-oxQCQ8EXqpSaTybryokbEM/LAqkG0L7OJuucllCg5roqRGIHE437Abus0Bn67P8TKJaYjyKxomg8wCvfmInjlg==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/messaging": "0.11.0", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/messaging-interop-types": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.1.1.tgz", - "integrity": "sha512-7XuY87zPh01EBaeS3s6co31Il5oGbPl5MxAg6Uj3fPv7PqJQlbwQ+B5k7CKSF/Y26tRxp+u+usxIvIWCSEA8CQ==" - }, - "@firebase/performance": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.5.17.tgz", - "integrity": "sha512-NDgzI5JYo6Itnj1FWhMkK3LtwKhtOnhC+WBkxezjzFVuCOornQjvu7ucAU1o2dHXh7MFruhHGFPsHyfkkMCljA==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/performance-compat": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.1.17.tgz", - "integrity": "sha512-Hci5MrDlRuqwVozq7LaSAufXXElz+AtmEQArix64kLRJqHhOu5K/8TpuZXM/klR6gnLyIrk+01CrAemH3zHpDw==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/performance": "0.5.17", - "@firebase/performance-types": "0.1.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/performance-types": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.1.1.tgz", - "integrity": "sha512-wiJRLBg8EPaYSGJqx7aqkZ3L5fULfZa9zOTs4C06K020g0zzJh9kUUO/0U3wvHz7zRQjJxTO8Jw4SDjxs3EZrA==" - }, - "@firebase/remote-config": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.3.15.tgz", - "integrity": "sha512-ZCyqoCaftoNvc2r4zPaqNV4OgC4sRHjcQI+agzXESnhDLnTY8DpCaQ0m9j6deHuxxDOgu8QPDb8psLbjR+9CgQ==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/installations": "0.5.16", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/remote-config-compat": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.1.16.tgz", - "integrity": "sha512-BWonzeqODnGki/fZ17zOnjJFR5CWbIOU0PmYGjWBnbkWxpFDdE3zNsz8JTVd/Mkt7y2PHFMYpLsyZ473E/62FQ==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/remote-config": "0.3.15", - "@firebase/remote-config-types": "0.2.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/remote-config-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.2.1.tgz", - "integrity": "sha512-1PGx4vKtMMd5uB6G1Nj2b8fOnJx7mIJGzkdyfhIM1oQx9k3dJ+pVu4StrNm46vHaD8ZlOQLr91YfUE43xSXwSg==" - }, - "@firebase/storage": { - "version": "0.9.14", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.9.14.tgz", - "integrity": "sha512-he8VAJ4BLkQdebnna15TI1/ymkwQTeKnjA/psKMAJ2+/UswD/68bCMKOlTrMvw6Flv3zc5YZk1xdL9DHR0i6wg==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/util": "1.7.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - } - }, - "@firebase/storage-compat": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.1.22.tgz", - "integrity": "sha512-uv33WnAEcxf2983Z03uhJmKc91LKSsRijFwut8xeoJamJoGAVj1Tc9Mio491aI1KZ+RMkNFghHL2FpxjuvxpPg==", - "requires": { - "@firebase/component": "0.5.21", - "@firebase/storage": "0.9.14", - "@firebase/storage-types": "0.6.1", - "@firebase/util": "1.7.3", - "tslib": "^2.1.0" - } - }, - "@firebase/storage-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.1.tgz", - "integrity": "sha512-/pkNzKiGCSjdBBZHPvWL1kkPZfM3pFJ38HPJE1xTHwLBwdrFb4JrmY+5/E4ma5ePsbejecIOD1SZhEKDB/JwUQ==", - "requires": {} - }, - "@firebase/util": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", - "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/webchannel-wrapper": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.8.1.tgz", - "integrity": "sha512-CJW8vxt6bJaBeco2VnlJjmCmAkrrtIdf0GGKvpAB4J5gw8Gi0rHb+qsgKp6LsyS5W6ALPLawLs7phZmw02dvLw==" - }, - "@grpc/grpc-js": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", - "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", - "requires": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "dependencies": { - "@grpc/proto-loader": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.3.tgz", - "integrity": "sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - } - }, - "protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "dependencies": { - "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - } - } - } - } - }, - "@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.11.3", - "yargs": "^16.2.0" - } - }, "@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -8866,12 +7517,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "@malept/cross-spawn-promise": { @@ -8881,49 +7532,6 @@ "dev": true, "requires": { "cross-spawn": "^7.0.1" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "@malept/flatpak-bundler": { @@ -8938,6 +7546,15 @@ "tmp-promise": "^3.0.2" }, "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -8960,6 +7577,12 @@ "universalify": "^2.0.0" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -8994,60 +7617,6 @@ "fastq": "^1.6.0" } }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -9152,11 +7721,6 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "peer": true }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, "@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -9164,6 +7728,11 @@ "dev": true, "optional": true }, + "@types/mixpanel-browser": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.38.0.tgz", + "integrity": "sha512-TR8rvsILnqXA7oiiGOxuMGXwvDeCoQDonXJB5UR+TYvEAFpiK8ReFj5LhZT+Xhm3NpI9aPoju30jB2ssorSUww==" + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -9171,9 +7740,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" }, "@types/plist": { "version": "3.0.2", @@ -9199,9 +7768,9 @@ "optional": true }, "@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", + "integrity": "sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -9423,6 +7992,23 @@ "dev": true, "requires": { "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "ajv": { @@ -9503,22 +8089,15 @@ "temp-file": "^3.4.0" }, "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "ms": "2.1.2" } }, - "ci-info": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.6.1.tgz", - "integrity": "sha512-up5ggbaDqOqJ4UqLKZ2naVkyqSJQgJi5lwD6b6mM748ysrghDBX0bx/qJTUHzw7zu6Mq4gycviSF5hJnwceD8w==", - "dev": true - }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -9530,15 +8109,6 @@ "universalify": "^2.0.0" } }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -9549,14 +8119,11 @@ "universalify": "^2.0.0" } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "semver": { "version": "7.3.8", @@ -9604,27 +8171,6 @@ "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } } }, "asn1": { @@ -9680,15 +8226,16 @@ "dev": true }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "base64-js": { "version": "1.5.1", @@ -9734,19 +8281,6 @@ "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "bluebird": { @@ -9903,21 +8437,13 @@ "temp-file": "^3.4.0" }, "dependencies": { - "ci-info": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.6.1.tgz", - "integrity": "sha512-up5ggbaDqOqJ4UqLKZ2naVkyqSJQgJi5lwD6b6mM748ysrghDBX0bx/qJTUHzw7zu6Mq4gycviSF5hJnwceD8w==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "ms": "2.1.2" } }, "fs-extra": { @@ -9931,15 +8457,6 @@ "universalify": "^2.0.0" } }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -9950,25 +8467,10 @@ "universalify": "^2.0.0" } }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "universalify": { @@ -9976,15 +8478,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -9996,6 +8489,23 @@ "requires": { "debug": "^4.3.2", "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "cacheable-request": { @@ -10012,14 +8522,6 @@ "responselike": "^1.0.2" }, "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -10043,9 +8545,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001431", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", - "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", + "version": "1.0.30001442", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", + "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==", "peer": true }, "caseless": { @@ -10083,9 +8585,9 @@ "dev": true }, "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", "dev": true }, "clean-css": { @@ -10194,7 +8696,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "config-chain": { "version": "1.1.13", @@ -10229,21 +8732,6 @@ "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } } }, "connected": { @@ -10252,9 +8740,10 @@ "integrity": "sha512-J8DB7618GkIYjc1RCxSdG3vffhhYRwHNEckjOGfwAbabQIMgKsL5c54IaWtisulEwoOEbEODEUak4kyJP2GJ/Q==" }, "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true }, "crc": { "version": "3.8.0", @@ -10283,16 +8772,14 @@ } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "crypto-js": { @@ -10317,11 +8804,11 @@ } }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "2.1.2" + "ms": "2.0.0" } }, "decompress-response": { @@ -10381,11 +8868,6 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "optional": true }, - "dialog-polyfill": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.4.10.tgz", - "integrity": "sha512-j5yGMkP8T00UFgyO+78OxiN5vC5dzRQF3BEio+LhNvDbyfxWBsi3sfPArDm54VloaJwy2hm3erEiDWqHRC8rzw==" - }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -10571,9 +9053,9 @@ }, "dependencies": { "@types/node": { - "version": "16.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.3.tgz", - "integrity": "sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==" + "version": "16.18.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", + "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==" } } }, @@ -10597,12 +9079,6 @@ "yargs": "^17.0.1" }, "dependencies": { - "ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", - "dev": true - }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -10614,15 +9090,6 @@ "universalify": "^2.0.0" } }, - "is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "requires": { - "ci-info": "^3.2.0" - } - }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -10638,21 +9105,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true - }, - "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } } } }, @@ -10671,6 +9123,15 @@ "fs-extra": "^9.0.1" }, "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -10693,6 +9154,12 @@ "universalify": "^2.0.0" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -10715,15 +9182,6 @@ "plist": "^3.0.1" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "isbinaryfile": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", @@ -10732,12 +9190,6 @@ "requires": { "buffer-alloc": "^1.2.0" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true } } }, @@ -10816,9 +9268,9 @@ } }, "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -10841,22 +9293,6 @@ "mime-types": "^2.1.35", "ts-node": "^10.9.1", "yargs": "17.6.2" - }, - "dependencies": { - "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - } } }, "enso-copy-plugin": { @@ -10875,29 +9311,21 @@ "ws": "^8.11.0" } }, - "enso-studio-common": { - "version": "file:lib/common", - "requires": { - "mime-types": "^2.1.35" - } - }, "enso-studio-content": { "version": "file:lib/content", "requires": { "@esbuild-plugins/node-globals-polyfill": "^0.1.1", "@esbuild-plugins/node-modules-polyfill": "^0.1.4", + "@types/mixpanel-browser": "^2.38.0", "@types/semver": "^7.3.9", "enso-copy-plugin": "^1.0.0", "enso-gui-server": "^1.0.0", - "enso-studio-common": "1.0.0", "esbuild": "^0.15.14", "esbuild-copy-static-files": "^0.1.0", "esbuild-dev-server": "^0.3.0", "esbuild-plugin-alias": "^0.2.1", "esbuild-plugin-time": "^1.0.0", "esbuild-plugin-yaml": "^0.0.1", - "firebase": "^9.14.0", - "firebaseui": "^6.0.2", "glob": "^8.0.3", "html-loader": "^4.2.0", "mixpanel-browser": "2.45.0", @@ -10910,8 +9338,6 @@ "dependencies": { "glob": { "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -10920,6 +9346,15 @@ "minimatch": "^5.0.1", "once": "^1.3.0" } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } } } }, @@ -10964,46 +9399,46 @@ "dev": true }, "esbuild": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.14.tgz", - "integrity": "sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", "dev": true, "requires": { - "@esbuild/android-arm": "0.15.14", - "@esbuild/linux-loong64": "0.15.14", - "esbuild-android-64": "0.15.14", - "esbuild-android-arm64": "0.15.14", - "esbuild-darwin-64": "0.15.14", - "esbuild-darwin-arm64": "0.15.14", - "esbuild-freebsd-64": "0.15.14", - "esbuild-freebsd-arm64": "0.15.14", - "esbuild-linux-32": "0.15.14", - "esbuild-linux-64": "0.15.14", - "esbuild-linux-arm": "0.15.14", - "esbuild-linux-arm64": "0.15.14", - "esbuild-linux-mips64le": "0.15.14", - "esbuild-linux-ppc64le": "0.15.14", - "esbuild-linux-riscv64": "0.15.14", - "esbuild-linux-s390x": "0.15.14", - "esbuild-netbsd-64": "0.15.14", - "esbuild-openbsd-64": "0.15.14", - "esbuild-sunos-64": "0.15.14", - "esbuild-windows-32": "0.15.14", - "esbuild-windows-64": "0.15.14", - "esbuild-windows-arm64": "0.15.14" + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" } }, "esbuild-android-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz", - "integrity": "sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", "dev": true, "optional": true }, "esbuild-android-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz", - "integrity": "sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", "dev": true, "optional": true }, @@ -11014,16 +9449,16 @@ "dev": true }, "esbuild-darwin-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz", - "integrity": "sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "dev": true, "optional": true }, "esbuild-darwin-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz", - "integrity": "sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", "dev": true, "optional": true }, @@ -11108,86 +9543,86 @@ "optional": true }, "esbuild-freebsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz", - "integrity": "sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", "dev": true, "optional": true }, "esbuild-freebsd-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz", - "integrity": "sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", "dev": true, "optional": true }, "esbuild-linux-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz", - "integrity": "sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", "dev": true, "optional": true }, "esbuild-linux-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz", - "integrity": "sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", "dev": true, "optional": true }, "esbuild-linux-arm": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz", - "integrity": "sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", "dev": true, "optional": true }, "esbuild-linux-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz", - "integrity": "sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", "dev": true, "optional": true }, "esbuild-linux-mips64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz", - "integrity": "sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", "dev": true, "optional": true }, "esbuild-linux-ppc64le": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz", - "integrity": "sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", "dev": true, "optional": true }, "esbuild-linux-riscv64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz", - "integrity": "sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", "dev": true, "optional": true }, "esbuild-linux-s390x": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz", - "integrity": "sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", "dev": true, "optional": true }, "esbuild-netbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz", - "integrity": "sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", "dev": true, "optional": true }, "esbuild-openbsd-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz", - "integrity": "sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", "dev": true, "optional": true }, @@ -11247,30 +9682,30 @@ } }, "esbuild-sunos-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz", - "integrity": "sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", "dev": true, "optional": true }, "esbuild-windows-32": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz", - "integrity": "sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", "dev": true, "optional": true }, "esbuild-windows-64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz", - "integrity": "sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", "dev": true, "optional": true }, "esbuild-windows-arm64": { - "version": "0.15.14", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz", - "integrity": "sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", "dev": true, "optional": true }, @@ -11375,22 +9810,26 @@ "yauzl": "^2.10.0" }, "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "pump": "^3.0.0" + "ms": "2.1.2" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, "extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true, - "optional": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true }, "fast-deep-equal": { "version": "3.1.3", @@ -11416,22 +9855,14 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" } }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -11453,6 +9884,17 @@ "dev": true, "requires": { "minimatch": "^5.0.1" + }, + "dependencies": { + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "fill-range": { @@ -11476,21 +9918,6 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } } }, "find-yarn-workspace-root": { @@ -11502,48 +9929,6 @@ "micromatch": "^4.0.2" } }, - "firebase": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.14.0.tgz", - "integrity": "sha512-wePrsf7W33mhT7RVXQavragoAgXb/NDm22vuhwJXkprrQ2Y9alrEKC5LTAtLJL3P2dHdDmeylS6PLZwWPEE79A==", - "requires": { - "@firebase/analytics": "0.8.4", - "@firebase/analytics-compat": "0.1.17", - "@firebase/app": "0.8.4", - "@firebase/app-check": "0.5.17", - "@firebase/app-check-compat": "0.2.17", - "@firebase/app-compat": "0.1.39", - "@firebase/app-types": "0.8.1", - "@firebase/auth": "0.20.11", - "@firebase/auth-compat": "0.2.24", - "@firebase/database": "0.13.10", - "@firebase/database-compat": "0.2.10", - "@firebase/firestore": "3.7.3", - "@firebase/firestore-compat": "0.2.3", - "@firebase/functions": "0.8.8", - "@firebase/functions-compat": "0.2.8", - "@firebase/installations": "0.5.16", - "@firebase/installations-compat": "0.1.16", - "@firebase/messaging": "0.11.0", - "@firebase/messaging-compat": "0.1.21", - "@firebase/performance": "0.5.17", - "@firebase/performance-compat": "0.1.17", - "@firebase/remote-config": "0.3.15", - "@firebase/remote-config-compat": "0.1.16", - "@firebase/storage": "0.9.14", - "@firebase/storage-compat": "0.1.22", - "@firebase/util": "1.7.3" - } - }, - "firebaseui": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/firebaseui/-/firebaseui-6.0.2.tgz", - "integrity": "sha512-Jwwn2I657loKrvedeCrwED9UibLFl8Cm0uH2ntDBSCpruWzG4HXlIWb35WsDdXMILRPQjJ1PwVwuRsrnsxcaXA==", - "requires": { - "dialog-polyfill": "^0.4.7", - "material-design-lite": "^1.2.0" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -11573,12 +9958,11 @@ "dev": true }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } @@ -11586,7 +9970,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "function-bind": { "version": "1.1.1", @@ -11611,9 +9996,9 @@ } }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "requires": { "pump": "^3.0.0" } @@ -11637,6 +10022,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11644,25 +10030,6 @@ "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - } } }, "glob-parent": { @@ -11716,9 +10083,9 @@ } }, "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, "requires": { "ini": "2.0.0" @@ -11769,6 +10136,16 @@ "p-cancelable": "^1.0.0", "to-readable-stream": "^1.0.0", "url-parse-lax": "^3.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + } } }, "graceful-fs": { @@ -11852,23 +10229,23 @@ } }, "html-minifier-terser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.0.0.tgz", - "integrity": "sha512-Adqk0b/pWKIQiGvEAuzPKpBKNHiwblr3QSGS7TTr6v+xXKV9AI2k4vWW+6Oytt6Z5SeBnfvYypKOnz8r75pz3Q==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.1.0.tgz", + "integrity": "sha512-BvPO2S7Ip0Q5qt+Y8j/27Vclj6uHC6av0TMoDn7/bJPhMWHI2UtR2e/zEgJn3/qYAmxumrGp9q4UHurL6mtW9Q==", "requires": { "camel-case": "^4.1.2", "clean-css": "5.2.0", - "commander": "^9.4.0", - "entities": "^4.3.1", + "commander": "^9.4.1", + "entities": "^4.4.0", "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^5.14.2" + "terser": "^5.15.1" }, "dependencies": { "commander": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", - "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==" + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" } } }, @@ -11896,11 +10273,6 @@ } } }, - "http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, "http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -11910,6 +10282,23 @@ "@tootallnate/once": "2", "agent-base": "6", "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "http-signature": { @@ -11931,6 +10320,23 @@ "requires": { "agent-base": "6", "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "iconv-corefoundation": { @@ -11953,11 +10359,6 @@ "safer-buffer": ">= 2.1.2 < 3.0.0" } }, - "idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -11970,11 +10371,6 @@ "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -11991,6 +10387,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -12020,12 +10417,12 @@ "dev": true }, "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, "requires": { - "ci-info": "^2.0.0" + "ci-info": "^3.2.0" } }, "is-docker": { @@ -12115,11 +10512,6 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -12148,27 +10540,6 @@ "chalk": "^4.0.2", "filelist": "^1.0.1", "minimatch": "^3.0.4" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } } }, "javascript-stringify": { @@ -12308,9 +10679,9 @@ "devOptional": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonfile": { @@ -12333,18 +10704,6 @@ "verror": "1.10.0" }, "dependencies": { - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -12358,17 +10717,6 @@ } } }, - "jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -12401,14 +10749,6 @@ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", "dev": true }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "requires": { - "immediate": "~3.0.5" - } - }, "load-bmfont": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", @@ -12446,9 +10786,9 @@ "peer": true }, "loader-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", - "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "requires": { "big.js": "^5.2.2", @@ -12461,16 +10801,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -12509,14 +10839,6 @@ "dev": true, "requires": { "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "make-error": { @@ -12534,11 +10856,6 @@ "escape-string-regexp": "^4.0.0" } }, - "material-design-lite": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/material-design-lite/-/material-design-lite-1.3.0.tgz", - "integrity": "sha512-ao76b0bqSTKcEMt7Pui+J/S3eVF0b3GWfuKUwfe2lP5DKlLZOwBq37e0/bXEzxrw7/SuHAuYAdoCwY6mAYhrsg==" - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -12595,12 +10912,24 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + } } }, "minimist": { @@ -12637,27 +10966,12 @@ "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "napi-build-utils": { "version": "1.0.2", @@ -12687,9 +11001,9 @@ } }, "node-abi": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.28.0.tgz", - "integrity": "sha512-fRlDb4I0eLcQeUvGq7IY3xHrSb0c9ummdvDSYWfT9+LKP+3jCKw/tKoqaM7r1BAoiAC6GtwyjaGnOz6B3OtF+A==", + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz", + "integrity": "sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==", "dev": true, "requires": { "semver": "^7.3.5" @@ -12713,18 +11027,10 @@ "dev": true, "optional": true }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", + "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", "peer": true }, "normalize-url": { @@ -12811,21 +11117,8 @@ "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, "param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -12873,9 +11166,9 @@ } }, "parse5": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.1.tgz", - "integrity": "sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "requires": { "entities": "^4.4.0" } @@ -12895,16 +11188,16 @@ } }, "patch-package": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.0.tgz", - "integrity": "sha512-tC3EqJmo74yKqfsMzELaFwxOAu6FH6t+FzFOsnWAuARm7/n2xB5AOeOueE221eM9gtMuIKMKpF9tBy/X2mNP0Q==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", + "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", "dev": true, "requires": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", "cross-spawn": "^6.0.5", "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^7.0.1", + "fs-extra": "^9.0.0", "is-ci": "^2.0.0", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", @@ -12914,17 +11207,112 @@ "slash": "^2.0.0", "tmp": "^0.0.33", "yaml": "^1.10.2" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "pend": { @@ -13027,6 +11415,11 @@ "requires": { "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -13061,11 +11454,6 @@ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -13077,26 +11465,6 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "optional": true }, - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -13184,17 +11552,14 @@ } }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { - "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" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "registry-auth-token": { @@ -13404,39 +11769,10 @@ "ajv-keywords": "^3.5.2" } }, - "selenium-webdriver": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.5.0.tgz", - "integrity": "sha512-9mSFii+lRwcnT2KUAB1kqvx6+mMiiQHH60Y0VUtr3kxxi3oZ3CV3B8e2nuJ7T4SPb+Q6VA0swswe7rYpez07Bg==", - "requires": { - "jszip": "^3.10.0", - "tmp": "^0.2.1", - "ws": ">=8.7.0" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "requires": { - "rimraf": "^3.0.0" - } - } - } - }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "semver-compare": { "version": "1.0.0", @@ -13451,14 +11787,6 @@ "dev": true, "requires": { "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "send": { @@ -13481,21 +11809,6 @@ "statuses": "2.0.1" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -13550,20 +11863,15 @@ "send": "0.18.0" } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "sharp": { - "version": "0.31.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.2.tgz", - "integrity": "sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==", + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz", + "integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==", "dev": true, "requires": { "color": "^4.2.3", @@ -13594,18 +11902,18 @@ } }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "signal-exit": { @@ -13769,11 +12077,20 @@ } }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "string-width": { @@ -13806,6 +12123,21 @@ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", "requires": { "debug": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "supports-color": { @@ -13845,19 +12177,6 @@ "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "temp-file": { @@ -13900,9 +12219,9 @@ } }, "terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", + "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", "requires": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", @@ -13928,24 +12247,12 @@ "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.0", "terser": "^5.14.1" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "peer": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - } } }, "tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", + "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==", "dev": true }, "tmp": { @@ -14028,11 +12335,6 @@ "punycode": "^2.1.1" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -14043,9 +12345,9 @@ } }, "ts-loader": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz", - "integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -14128,9 +12430,9 @@ } }, "typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true }, "unique-string": { @@ -14184,6 +12486,21 @@ "xdg-basedir": "^4.0.0" }, "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -14229,7 +12546,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "utils-merge": { "version": "1.0.1", @@ -14258,15 +12576,6 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" - }, - "dependencies": { - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "optional": true - } } }, "watchpack": { @@ -14279,15 +12588,10 @@ "graceful-fs": "^4.1.2" } }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, "webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", "peer": true, "requires": { "@types/eslint-scope": "^3.7.3", @@ -14322,34 +12626,10 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "peer": true }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -14392,9 +12672,9 @@ } }, "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", "requires": {} }, "xdg-basedir": { @@ -14480,42 +12760,25 @@ }, "dependencies": { "yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", "dev": true } } }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "dependencies": { - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" - } + "yargs-parser": "^21.1.1" } }, "yargs-parser": { diff --git a/app/ide-desktop/package.json b/app/ide-desktop/package.json index 6a0bf5b663..8e55874488 100644 --- a/app/ide-desktop/package.json +++ b/app/ide-desktop/package.json @@ -17,7 +17,6 @@ "private": true, "workspaces": [ "lib/client", - "lib/common", "lib/content", "lib/copy-plugin", "lib/icons", diff --git a/build/base/Cargo.toml b/build/base/Cargo.toml index e74d0c59b2..f276639d97 100644 --- a/build/base/Cargo.toml +++ b/build/base/Cargo.toml @@ -11,5 +11,5 @@ futures-util = "0.3.24" futures = "0.3.24" serde = "1.0.145" serde_json = "1.0.85" -serde_yaml = "0.9.13" +serde_yaml = { workspace = true } tracing = "0.1.36" diff --git a/build/base/src/extensions/path.rs b/build/base/src/extensions/path.rs index cfe98ef7aa..4831258d81 100644 --- a/build/base/src/extensions/path.rs +++ b/build/base/src/extensions/path.rs @@ -5,6 +5,7 @@ use crate::prelude::*; use crate::extensions::os_str::OsStrExt; use serde::de::DeserializeOwned; +use std::env::consts::EXE_EXTENSION; @@ -141,6 +142,24 @@ pub trait PathExt: AsRef { ret.extend(self.as_ref().file_name()); ret } + + /// Replace the filename extension with the default executable extension for the current OS. + /// + /// ``` + /// # use enso_build_base::prelude::*; + /// let path = Path::new("foo").with_executable_extension(); + /// // Windows: + /// #[cfg(target_os = "windows")] + /// assert_eq!(path, Path::new("foo.exe")); + /// // Other platforms: + /// #[cfg(not(target_os = "windows"))] + /// assert_eq!(path, Path::new("foo")); + /// ``` + fn with_executable_extension(&self) -> PathBuf { + let mut ret = self.as_ref().to_path_buf(); + ret.set_extension(EXE_EXTENSION); + ret + } } impl> PathExt for T {} diff --git a/build/build/Cargo.toml b/build/build/Cargo.toml index 7cbc9c10c5..2752fda720 100644 --- a/build/build/Cargo.toml +++ b/build/build/Cargo.toml @@ -33,6 +33,7 @@ handlebars = "4.3.5" heck = "0.4.0" humantime = "2.1.0" enso-build-base = { path = "../base" } +ensogl-pack = { path = "../../lib/rust/ensogl/pack" } ide-ci = { path = "../ci_utils" } indexmap = "1.7.0" indicatif = "0.17.1" @@ -42,9 +43,7 @@ lazy_static = "1.4.0" mime = "0.3.16" new_mime_guess = "4.0.1" nix = { workspace = true } -octocrab = { git = "https://github.com/enso-org/octocrab", default-features = false, features = [ - "rustls" -] } +octocrab = { workspace = true } ouroboros = "0.15.0" paste = "1.0.7" path-absolutize = "3.0.11" @@ -55,14 +54,14 @@ port_check = "0.1.5" pretty_env_logger = "0.4.0" pulldown-cmark = "0.9.1" rand = "0.8.4" -regex = "1.5.4" +regex = { workspace = true } reqwest = { version = "0.11.5", default-features = false, features = [ "stream" ] } semver = { version = "1.0.4", features = ["serde"] } serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.68" -serde_yaml = "0.9.10" +serde_yaml = { workspace = true } scopeguard = "1.1.0" shrinkwraprs = "0.3.0" strum = { version = "0.24.0", features = ["derive"] } @@ -84,4 +83,4 @@ zip = { version = "0.6.2", default-features = false, features = ["deflate"] } [build-dependencies] ide-ci = { path = "../ci_utils" } -serde_yaml = "0.9.10" +serde_yaml = { workspace = true } diff --git a/build/build/examples/experiments.rs b/build/build/examples/experiments.rs index 98d11afbf3..b951dc0ced 100644 --- a/build/build/examples/experiments.rs +++ b/build/build/examples/experiments.rs @@ -1,6 +1,6 @@ use enso_build::prelude::*; -use enso_build::setup_octocrab; +use ide_ci::github::setup_octocrab; use ide_ci::github::Repo; use octocrab::models::ReleaseId; diff --git a/build/build/paths.yaml b/build/build/paths.yaml index 3f43c847c5..e7133e2ba5 100644 --- a/build/build/paths.yaml +++ b/build/build/paths.yaml @@ -11,6 +11,7 @@ promote.yml: release.yml: scala-new.yml: + shader-tools.yml: app/: gui/: ide-desktop/: @@ -52,12 +53,12 @@ # Final WASM artifacts in `dist` directory. wasm/: - ? path: ide.wasm - var: wasm_main - ? path: ide_bg.wasm - var: wasm_main_raw - ? path: ide.js - var: wasm_glue + shaders/: # Optimized shaders that contain main function code only. + index.cjs: # The main JS bundle to load WASM and JS wasm-pack bundles. + index.d.ts: # TypeScript types interface file. + pkg.js: # The `pks.js` artifact of wasm-pack WITH bundled snippets. + pkg.wasm: # The `pks_bg.wasm` artifact of wasm-pack. + pkg-opt.wasm: # The optimized `pks_bg.wasm`. distribution/: editions/: .yaml: diff --git a/build/cli/src/ci_gen.rs b/build/build/src/ci_gen.rs similarity index 94% rename from build/cli/src/ci_gen.rs rename to build/build/src/ci_gen.rs index 254e6f2816..238fb8572b 100644 --- a/build/cli/src/ci_gen.rs +++ b/build/build/src/ci_gen.rs @@ -4,11 +4,11 @@ use crate::ci_gen::job::expose_os_specific_signing_secret; use crate::ci_gen::job::plain_job; use crate::ci_gen::job::plain_job_customized; use crate::ci_gen::job::RunsOn; +use crate::version::promote::Designation; +use crate::version::ENSO_EDITION; +use crate::version::ENSO_RELEASE_MODE; +use crate::version::ENSO_VERSION; -use enso_build::version::promote::Designation; -use enso_build::version::ENSO_EDITION; -use enso_build::version::ENSO_RELEASE_MODE; -use enso_build::version::ENSO_VERSION; use ide_ci::actions::workflow::definition::checkout_repo_step; use ide_ci::actions::workflow::definition::is_non_windows_runner; use ide_ci::actions::workflow::definition::is_windows_runner; @@ -34,6 +34,7 @@ use ide_ci::actions::workflow::definition::WorkflowCall; use ide_ci::actions::workflow::definition::WorkflowDispatch; use ide_ci::actions::workflow::definition::WorkflowDispatchInput; use ide_ci::actions::workflow::definition::WorkflowDispatchInputType; +use ide_ci::actions::workflow::definition::WorkflowToWrite; use strum::IntoEnumIterator; @@ -171,7 +172,7 @@ pub fn setup_customized_script_steps( command_line: impl AsRef, customize: impl FnOnce(Step) -> Vec, ) -> Vec { - use enso_build::ci::labels::CLEAN_BUILD_REQUIRED; + use crate::ci::labels::CLEAN_BUILD_REQUIRED; // Check if the pull request has a "Clean required" label. let pre_clean_condition = format!("contains(github.event.pull_request.labels.*.name, '{CLEAN_BUILD_REQUIRED}')",); @@ -495,14 +496,22 @@ pub fn benchmark() -> Result { Ok(workflow) } - -pub fn generate(repo_root: &enso_build::paths::generated::RepoRootGithubWorkflows) -> Result { - repo_root.changelog_yml.write_as_yaml(&changelog()?)?; - repo_root.nightly_yml.write_as_yaml(&nightly()?)?; - repo_root.scala_new_yml.write_as_yaml(&backend()?)?; - repo_root.gui_yml.write_as_yaml(&gui()?)?; - repo_root.benchmark_yml.write_as_yaml(&benchmark()?)?; - repo_root.release_yml.write_as_yaml(&release()?)?; - repo_root.promote_yml.write_as_yaml(&promote()?)?; - Ok(()) +/// Generate workflows for the CI. +pub fn generate( + repo_root: &crate::paths::generated::RepoRootGithubWorkflows, +) -> Result> { + let workflows = [ + (repo_root.changelog_yml.to_path_buf(), changelog()?), + (repo_root.nightly_yml.to_path_buf(), nightly()?), + (repo_root.scala_new_yml.to_path_buf(), backend()?), + (repo_root.gui_yml.to_path_buf(), gui()?), + (repo_root.benchmark_yml.to_path_buf(), benchmark()?), + (repo_root.release_yml.to_path_buf(), release()?), + (repo_root.promote_yml.to_path_buf(), promote()?), + ]; + let workflows = workflows + .into_iter() + .map(|(path, workflow)| WorkflowToWrite { workflow, path, source: module_path!().into() }) + .collect(); + Ok(workflows) } diff --git a/build/cli/src/ci_gen/job.rs b/build/build/src/ci_gen/job.rs similarity index 90% rename from build/cli/src/ci_gen/job.rs rename to build/build/src/ci_gen/job.rs index 0c203c565f..4e77f68246 100644 --- a/build/cli/src/ci_gen/job.rs +++ b/build/build/src/ci_gen/job.rs @@ -155,13 +155,13 @@ impl JobArchetype for DeployRuntime { plain_job_customized(&os, "Upload Runtime to ECR", "release deploy-runtime", |step| { let step = step .with_secret_exposed_as("CI_PRIVATE_TOKEN", "GITHUB_TOKEN") - .with_env("ENSO_BUILD_ECR_REPOSITORY", enso_build::aws::ecr::runtime::NAME) + .with_env("crate_ECR_REPOSITORY", crate::aws::ecr::runtime::NAME) .with_secret_exposed_as(secret::ECR_PUSH_RUNTIME_ACCESS_KEY_ID, "AWS_ACCESS_KEY_ID") .with_secret_exposed_as( secret::ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY, "AWS_SECRET_ACCESS_KEY", ) - .with_env("AWS_DEFAULT_REGION", enso_build::aws::ecr::runtime::REGION); + .with_env("AWS_DEFAULT_REGION", crate::aws::ecr::runtime::REGION); vec![step] }) } @@ -187,32 +187,29 @@ impl JobArchetype for DeployGui { pub fn expose_os_specific_signing_secret(os: OS, step: Step) -> Step { match os { OS::Windows => step - .with_secret_exposed_as( - secret::WINDOWS_CERT_PATH, - &enso_build::ide::web::env::WIN_CSC_LINK, - ) + .with_secret_exposed_as(secret::WINDOWS_CERT_PATH, &crate::ide::web::env::WIN_CSC_LINK) .with_secret_exposed_as( secret::WINDOWS_CERT_PASSWORD, - &enso_build::ide::web::env::WIN_CSC_KEY_PASSWORD, + &crate::ide::web::env::WIN_CSC_KEY_PASSWORD, ), OS::MacOS => step .with_secret_exposed_as( secret::APPLE_CODE_SIGNING_CERT, - &enso_build::ide::web::env::CSC_LINK, + &crate::ide::web::env::CSC_LINK, ) .with_secret_exposed_as( secret::APPLE_CODE_SIGNING_CERT_PASSWORD, - &enso_build::ide::web::env::CSC_KEY_PASSWORD, + &crate::ide::web::env::CSC_KEY_PASSWORD, ) .with_secret_exposed_as( secret::APPLE_NOTARIZATION_USERNAME, - &enso_build::ide::web::env::APPLEID, + &crate::ide::web::env::APPLEID, ) .with_secret_exposed_as( secret::APPLE_NOTARIZATION_PASSWORD, - &enso_build::ide::web::env::APPLEIDPASS, + &crate::ide::web::env::APPLEIDPASS, ) - .with_env(&enso_build::ide::web::env::CSC_IDENTITY_AUTO_DISCOVERY, "true"), + .with_env(&crate::ide::web::env::CSC_IDENTITY_AUTO_DISCOVERY, "true"), _ => step, } } diff --git a/build/cli/src/ci_gen/step.rs b/build/build/src/ci_gen/step.rs similarity index 98% rename from build/cli/src/ci_gen/step.rs rename to build/build/src/ci_gen/step.rs index 4738205944..1c5ce1ec2b 100644 --- a/build/cli/src/ci_gen/step.rs +++ b/build/build/src/ci_gen/step.rs @@ -1,6 +1,7 @@ use crate::prelude::*; -use enso_build::paths; +use crate::paths; + use ide_ci::actions::workflow::definition::env_expression; use ide_ci::actions::workflow::definition::Step; diff --git a/build/build/src/engine/artifact.rs b/build/build/src/engine/artifact.rs index 5d1483ccf8..6cf8b48d46 100644 --- a/build/build/src/engine/artifact.rs +++ b/build/build/src/engine/artifact.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -use ide_ci::github::release::ReleaseHandle; +use ide_ci::github::release; use octocrab::models::repos::Asset; @@ -34,7 +34,7 @@ pub trait IsArtifact: AsRef + Send + Sync { Ok(self.as_ref().try_parent()?.try_file_name()?.to_os_string()) } - fn upload_as_asset(&self, release: ReleaseHandle) -> BoxFuture<'static, Result> { + fn upload_as_asset(&self, release: release::Handle) -> BoxFuture<'static, Result> { let path = self.as_ref().to_path_buf(); let name = self.asset_file_stem(); async move { release.upload_compressed_dir_as(path, name?).await }.boxed() diff --git a/build/build/src/engine/context.rs b/build/build/src/engine/context.rs index 3187ef8338..b022456f52 100644 --- a/build/build/src/engine/context.rs +++ b/build/build/src/engine/context.rs @@ -550,7 +550,7 @@ impl RunContext { ReleaseCommand::Upload => { let artifacts = self.build().await?; let release_id = crate::env::ENSO_RELEASE_ID.get()?; - let release = ide_ci::github::release::ReleaseHandle::new( + let release = ide_ci::github::release::Handle::new( &self.inner.octocrab, repo, release_id, diff --git a/build/build/src/ide/web.rs b/build/build/src/ide/web.rs index 233bfd6569..3114f60dd1 100644 --- a/build/build/src/ide/web.rs +++ b/build/build/src/ide/web.rs @@ -9,19 +9,24 @@ use crate::project::ProcessWrapper; use anyhow::Context; use futures_util::future::try_join; use futures_util::future::try_join4; -use ide_ci::github::RepoRef; use ide_ci::io::download_all; use ide_ci::program::command; use ide_ci::program::EMPTY_ARGS; use ide_ci::programs::node::NpmCommand; use ide_ci::programs::Npm; -use octocrab::models::repos::Content; use std::process::Stdio; use tempfile::TempDir; use tokio::process::Child; use tracing::Span; +// ============== +// === Export === +// ============== + +pub mod google_font; + + lazy_static! { /// Path to the file with build information that is consumed by the JS part of the IDE. @@ -35,10 +40,6 @@ pub const IDE_ASSETS_URL: &str = pub const ARCHIVED_ASSET_FILE: &str = "ide-assets-main/content/assets/"; -pub const GOOGLE_FONTS_REPOSITORY: RepoRef = RepoRef { owner: "google", name: "fonts" }; - -pub const GOOGLE_FONT_DIRECTORY: &str = "ofl"; - pub mod env { use super::*; @@ -49,8 +50,10 @@ pub mod env { ENSO_BUILD_PROJECT_MANAGER, PathBuf; ENSO_BUILD_GUI, PathBuf; ENSO_BUILD_ICONS, PathBuf; - ENSO_BUILD_GUI_WASM, PathBuf; - ENSO_BUILD_GUI_JS_GLUE, PathBuf; + /// List of files that should be copied to the Gui. + ENSO_BUILD_GUI_WASM_ARTIFACTS, Vec; + /// The main JS bundle to load WASM and JS wasm-pack bundles. + ENSO_BUILD_GUI_ENSOGL_APP, PathBuf; ENSO_BUILD_GUI_ASSETS, PathBuf; ENSO_BUILD_IDE_BUNDLED_ENGINE_VERSION, Version; ENSO_BUILD_PROJECT_MANAGER_IN_BUNDLE_PATH, PathBuf; @@ -101,28 +104,6 @@ impl command::FallibleManipulator for IconsArtifacts { } } -#[context("Failed to download Google font '{family}'.")] -#[instrument(fields(output_path = %output_path.as_ref().display()), ret, err, skip(octocrab))] -pub async fn download_google_font( - octocrab: &Octocrab, - family: &str, - output_path: impl AsRef, -) -> Result> { - let destination_dir = output_path.as_ref(); - let repo = GOOGLE_FONTS_REPOSITORY.handle(octocrab); - let path = format!("{GOOGLE_FONT_DIRECTORY}/{family}"); - let files = repo.repos().get_content().path(path).send().await?; - let ttf_files = - files.items.into_iter().filter(|file| file.name.ends_with(".ttf")).collect_vec(); - for file in &ttf_files { - let destination_file = destination_dir.join(&file.name); - let url = file.download_url.as_ref().context("Missing 'download_url' in the reply.")?; - let reply = ide_ci::io::web::client::download(&octocrab.client, url).await?; - ide_ci::io::web::stream_to_file(reply, &destination_file).await?; - } - Ok(ttf_files) -} - /// Fill the directory under `output_path` with the assets. pub async fn download_js_assets(output_path: impl AsRef) -> Result { let output = output_path.as_ref(); @@ -175,7 +156,8 @@ impl> ContentEnvironment { let installation = ide.install(); let asset_dir = TempDir::new()?; let assets_download = download_js_assets(&asset_dir); - let fonts_download = download_google_font(&ide.octocrab, "mplus1", &asset_dir); + let fonts_download = + google_font::download_google_font(&ide.cache, &ide.octocrab, "mplus1", &asset_dir); let (wasm, _, _, _) = try_join4(wasm, installation, assets_download, fonts_download).await?; ide.write_build_info(build_info)?; @@ -187,10 +169,13 @@ impl, Output: AsRef> command::FallibleManipulator for ContentEnvironment { fn try_applying(&self, command: &mut C) -> Result { + let artifacts_for_gui = + self.wasm.files_to_ship().into_iter().map(|file| file.to_path_buf()).collect_vec(); + command .set_env(env::ENSO_BUILD_GUI, self.output_path.as_ref())? - .set_env(env::ENSO_BUILD_GUI_WASM, &self.wasm.wasm())? - .set_env(env::ENSO_BUILD_GUI_JS_GLUE, &self.wasm.js_glue())? + .set_env(env::ENSO_BUILD_GUI_WASM_ARTIFACTS, &artifacts_for_gui)? + .set_env(env::ENSO_BUILD_GUI_ENSOGL_APP, &self.wasm.ensogl_app())? .set_env(env::ENSO_BUILD_GUI_ASSETS, self.asset_dir.as_ref())?; Ok(()) } @@ -211,10 +196,12 @@ pub fn target_os_flag(os: OS) -> Result<&'static str> { } } -#[derive(Clone, Debug)] +#[derive(Clone, Derivative)] +#[derivative(Debug)] pub struct IdeDesktop { pub build_sbt: generated::RepoRootBuildSbt, pub package_dir: generated::RepoRootAppIdeDesktop, + #[derivative(Debug = "ignore")] pub octocrab: Octocrab, pub cache: ide_ci::cache::Cache, } diff --git a/build/build/src/ide/web/google_font.rs b/build/build/src/ide/web/google_font.rs new file mode 100644 index 0000000000..0c12520ec5 --- /dev/null +++ b/build/build/src/ide/web/google_font.rs @@ -0,0 +1,178 @@ +//! Downloading Google Fonts. + +use crate::prelude::*; + +use ide_ci::cache::Cache; +use ide_ci::cache::Storable; +use ide_ci::github; +use ide_ci::github::RepoRef; +use octocrab::models::repos; + + + +// ================= +// === Constants === +// ================= + +/// Google Fonts repository. +pub const GOOGLE_FONTS_REPOSITORY: RepoRef = RepoRef { owner: "google", name: "fonts" }; + +/// Path to the directory on the Google Fonts repository where we get the fonts from. +/// +/// The directory name denotes the license of the fonts. In our case this is SIL OPEN FONT LICENSE +/// Version 1.1, commonly known as OFL. +pub const GOOGLE_FONT_DIRECTORY: &str = "ofl"; + +/// We keep dependency to a fixed commit, so we can safely cache it. +/// +/// There are no known reasons not to bump this. +pub const GOOGLE_FONT_SHA1: &str = "ea893a43af7c5ab5ccee189fc2720788d99887ed"; + + +// ============== +// === Family === +// ============== + +/// Identifies uniquely a source of font family download. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Family { + /// Remote repository with fonts. + pub repo: github::Repo, + /// Which commit we want to be downloaded. + pub r#ref: String, + /// Font family. It corresponds to the subdirectories names (under the top-level + /// license-denoting directories). + pub name: String, +} + +impl Family { + /// List content items in the repository that contain TTF files for the given font family. + pub async fn list_ttf( + &self, + handle: github::repo::Handle, + ) -> Result> { + let path = format!("{GOOGLE_FONT_DIRECTORY}/{}", self.name); + let files = handle.repos().get_content().r#ref(&self.r#ref).path(path).send().await?; + Ok(files.items.into_iter().filter(|file| file.name.ends_with(".ttf")).collect()) + } +} + + +// ==================== +// === DownloadFont === +// ==================== + +/// Description of the job to download the fonts. +#[derive(Derivative, Clone)] +#[derivative(Debug)] +pub struct DownloadFont { + pub family: Family, + /// Possible authentication to GitHub (to get bigger rate limit). + #[derivative(Debug = "ignore")] + pub octocrab: Octocrab, +} + +impl DownloadFont { + /// Get a handle to the remote repository with the fonts. + pub fn handle(&self) -> github::repo::Handle { + self.family.repo.handle(&self.octocrab) + } + + /// Download the font family to the given directory. They will be placed in the output + /// directory. The function returns relative paths to the downloaded files. + pub async fn download(&self, output_path: impl AsRef) -> Result> { + let files = self.family.list_ttf(self.handle()).await?; + let mut ret = Vec::new(); + for file in &files { + let destination_file = output_path.as_ref().join(&file.name); + let url = file.download_url.as_ref().context("Missing 'download_url' in the reply.")?; + let reply = ide_ci::io::web::client::download(&self.octocrab.client, url).await?; + ide_ci::io::web::stream_to_file(reply, &destination_file).await?; + ret.push(file.name.as_str().into()); + } + Ok(ret) + } +} + +impl Storable for DownloadFont { + /// In metadata form we just store paths relative to the store. + type Metadata = Vec; + /// Here paths are absolute. + type Output = Vec; + type Key = Family; + + fn generate( + &self, + _cache: Cache, + store: PathBuf, + ) -> BoxFuture<'static, Result> { + let this = self.clone(); + async move { + let fonts = this.download(&store).await?; + Ok(fonts) + } + .boxed() + } + + fn adapt( + &self, + cache: PathBuf, + mut metadata: Self::Metadata, + ) -> BoxFuture<'static, Result> { + async move { + for font in &mut metadata { + *font = cache.join(&font); + } + Ok(metadata) + } + .boxed() + } + + fn key(&self) -> Self::Key { + self.family.clone() + } +} + +// =================== +// === Entry Point === +// =================== + +pub async fn download_google_font( + cache: &Cache, + octocrab: &Octocrab, + family: &str, + output_path: impl AsRef, +) -> Result> { + let family = Family { + repo: GOOGLE_FONTS_REPOSITORY.into(), + r#ref: GOOGLE_FONT_SHA1.into(), + name: family.into(), + }; + let font = DownloadFont { family, octocrab: octocrab.clone() }; + let cached_fonts = cache.get(font).await?; + let copy_futures = + cached_fonts.into_iter().map(|font| ide_ci::fs::tokio::copy_to(font, &output_path)); + let result = futures::future::join_all(copy_futures).await.into_iter().try_collect()?; + Ok(result) +} + +// ============= +// === Tests === +// ============= + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + #[ignore] + async fn new_download() -> Result { + setup_logging()?; + let path = r"C:\temp\google_fonts2"; + let octocrab = ide_ci::github::setup_octocrab().await?; + let cache = Cache::new_default().await?; + let aaa = download_google_font(&cache, &octocrab, "mplus1", path).await?; + dbg!(aaa); + Ok(()) + } +} diff --git a/build/build/src/lib.rs b/build/build/src/lib.rs index dda8d5f082..5cea94cf1d 100644 --- a/build/build/src/lib.rs +++ b/build/build/src/lib.rs @@ -49,6 +49,7 @@ pub mod aws; pub mod bump_version; pub mod changelog; pub mod ci; +pub mod ci_gen; pub mod config; pub mod context; pub mod engine; @@ -110,62 +111,10 @@ pub fn get_java_major_version(build_sbt_contents: &str) -> Result Result { - fn get_token_from_file() -> Result { - let path = - dirs::home_dir().context("Failed to locate home directory.")?.join("GITHUB_TOKEN"); - debug!("Looking for GitHub token in the file {}", path.display()); - let content = ide_ci::fs::read_to_string(path)?; - Ok(content.trim().into()) - } - - ide_ci::env::expect_var("GITHUB_TOKEN") - .inspect(|_| debug!("Will use GITHUB_TOKEN environment variable.")) - .inspect_err(|e| debug!("Failed to retrieve GitHub authentication from environment: {e}")) - .or_else(|_| get_token_from_file()) -} - -#[context("Failed to setup GitHub API client.")] -pub async fn setup_octocrab() -> Result { - let builder = octocrab::OctocrabBuilder::new(); - let octocrab = if let Ok(access_token) = retrieve_github_access_token() { - let octocrab = builder.personal_token(access_token).build()?; - let username = octocrab - .current() - .user() - .await - .inspect_err(|e| warn!("Failed to retrieve GitHub username: {e}")) - .map_or_else(|_| "N/A".to_string(), |user| user.login); - info!("Using GitHub API with personal access token. Authenticated as {username}.",); - octocrab - } else { - info!("No GitHub Personal Access Token found. Will use anonymous API access."); - warn!( - "Anonymous GitHub API access is rate-limited. If you are experiencing issues, please \ - set the GITHUB_TOKEN environment variable." - ); - warn!( - "Additionally some APIs may not be available to anonymous users. This primarily \ - pertains the release-related APIs." - ); - builder.build()? - }; - - match octocrab.ratelimit().get().await { - Ok(rate) => info!( - "GitHub API rate limit: {}/{}.", - rate.resources.core.used, rate.resources.core.limit - ), - Err(e) => bail!( - "Failed to get rate limit info: {e}. GitHub Personal Access Token might be invalid." - ), - } - Ok(octocrab) -} - #[cfg(test)] mod tests { use super::*; + use ide_ci::github::setup_octocrab; #[tokio::test] #[ignore] diff --git a/build/build/src/project/wasm.rs b/build/build/src/project/wasm.rs index b57b689637..827fedcf89 100644 --- a/build/build/src/project/wasm.rs +++ b/build/build/src/project/wasm.rs @@ -15,6 +15,7 @@ use derivative::Derivative; use ide_ci::cache; use ide_ci::fs::compressed_size; use ide_ci::fs::copy_file_if_different; +use ide_ci::goodies::shader_tools::ShaderTools; use ide_ci::programs::cargo; use ide_ci::programs::wasm_opt; use ide_ci::programs::wasm_opt::WasmOpt; @@ -217,6 +218,8 @@ impl IsTarget for Wasm { // We want to be able to pass --profile this way. WasmPack.require_present_that(VersionReq::parse(">=0.10.1")?).await?; + ShaderTools.install_if_missing(&cache).await?; + let BuildInput { crate_path, wasm_opt_options, @@ -236,34 +239,43 @@ impl IsTarget for Wasm { info!("Building wasm."); let temp_dir = tempdir()?; let temp_dist = RepoRootDistWasm::new_root(temp_dir.path()); - let mut command = WasmPack.cmd()?; - command - .current_dir(&repo_root) - .kill_on_drop(true) - .env_remove(ide_ci::programs::rustup::env::RUSTUP_TOOLCHAIN.name()) - .build() - .arg(wasm_pack::Profile::from(*profile)) - .target(wasm_pack::Target::Web) - .output_directory(&temp_dist) - .output_name(OUTPUT_NAME) - .arg(crate_path) - .arg("--") - .apply(&cargo::Color::Always) - .args(extra_cargo_options); + ensogl_pack::build( + ensogl_pack::WasmPackOutputs { + out_dir: temp_dist.path.clone(), + out_name: OUTPUT_NAME.into(), + }, + |args| { + let mut command = WasmPack.cmd()?; + command + .current_dir(&repo_root) + .kill_on_drop(true) + .env_remove(ide_ci::programs::rustup::env::RUSTUP_TOOLCHAIN.name()) + .build() + .arg(wasm_pack::Profile::from(*profile)) + .target(wasm_pack::Target::Web) + .output_directory(args.out_dir) + .output_name(args.out_name) + .arg(crate_path) + .arg("--") + .apply(&cargo::Color::Always) + .args(extra_cargo_options); - if let Some(profiling_level) = profiling_level { - command.set_env(env::ENSO_MAX_PROFILING_LEVEL, &profiling_level)?; - } - command.set_env(env::ENSO_MAX_LOG_LEVEL, &log_level)?; - command.set_env(env::ENSO_MAX_UNCOLLAPSED_LOG_LEVEL, &uncollapsed_log_level)?; - command.run_ok().await?; + if let Some(profiling_level) = profiling_level { + command.set_env(env::ENSO_MAX_PROFILING_LEVEL, &profiling_level)?; + } + command.set_env(env::ENSO_MAX_LOG_LEVEL, &log_level)?; + command.set_env(env::ENSO_MAX_UNCOLLAPSED_LOG_LEVEL, &uncollapsed_log_level)?; + Ok(command) + }, + ) + .await?; Self::finalize_wasm(wasm_opt_options, *skip_wasm_opt, *profile, &temp_dist).await?; ide_ci::fs::create_dir_if_missing(&destination)?; let ret = RepoRootDistWasm::new_root(&destination); ide_ci::fs::copy(&temp_dist, &ret)?; - inner.perhaps_check_size(&ret.wasm_main).await?; + inner.perhaps_check_size(&ret.pkg_opt_wasm).await?; Ok(Artifact(ret)) } .instrument(span) @@ -401,14 +413,26 @@ impl Artifact { pub fn new(path: impl Into) -> Self { Self(RepoRootDistWasm::new_root(path)) } - pub fn wasm(&self) -> &Path { - &self.0.wasm_main + + /// The main JS bundle to load WASM and JS wasm-pack bundles. + pub fn ensogl_app(&self) -> &Path { + &self.0.index_cjs } - pub fn js_glue(&self) -> &Path { - &self.0.wasm_glue - } - pub fn dir(&self) -> &Path { - &self.0.path + + /// Files that should be shipped in the Gui bundle. + pub fn files_to_ship(&self) -> Vec<&Path> { + // We explicitly deconstruct object, so when new fields are added, we will be forced to + // consider whether they should be shipped or not. + let RepoRootDistWasm { + path: _, + shaders, + index_cjs: _, + index_d_ts: _, + pkg_js, + pkg_wasm: _, + pkg_opt_wasm, + } = &self.0; + vec![shaders.as_path(), pkg_js.as_path(), pkg_opt_wasm.as_path()] } } @@ -523,12 +547,12 @@ impl Wasm { } wasm_opt_command .args(wasm_opt_options) - .arg(&temp_dist.wasm_main_raw) - .apply(&wasm_opt::Output(&temp_dist.wasm_main)) + .arg(&temp_dist.pkg_wasm) + .apply(&wasm_opt::Output(&temp_dist.pkg_opt_wasm)) .run_ok() .await?; } else { - copy_file_if_different(&temp_dist.wasm_main_raw, &temp_dist.wasm_main)?; + copy_file_if_different(&temp_dist.pkg_wasm, &temp_dist.pkg_opt_wasm)?; } Ok(()) } diff --git a/build/build/src/release.rs b/build/build/src/release.rs index 844a01f521..c07fd41bb5 100644 --- a/build/build/src/release.rs +++ b/build/build/src/release.rs @@ -152,13 +152,14 @@ pub async fn publish_release(context: &BuildContext) -> Result { let BuildContext { inner: project::Context { .. }, triple, .. } = context; let release_id = crate::env::ENSO_RELEASE_ID.get()?; + let release_handle = remote_repo.release_handle(release_id); debug!("Looking for release with id {release_id} on github."); - let release = remote_repo.repos().releases().get_by_id(release_id).await?; + let release = release_handle.get().await?; ensure!(release.draft, "Release has been already published!"); debug!("Found the target release, will publish it."); - remote_repo.repos().releases().update(release.id.0).draft(false).send().await?; + release_handle.publish().await?; debug!("Done. Release URL: {}", release.url); let temp = tempdir()?; diff --git a/build/build/src/repo.rs b/build/build/src/repo.rs index 99360f3025..0d1c84bb7b 100644 --- a/build/build/src/repo.rs +++ b/build/build/src/repo.rs @@ -1,3 +1,5 @@ +//! Module for dealing with repositories owned by our project. + use crate::prelude::*; @@ -26,18 +28,15 @@ pub fn looks_like_enso_repository_root(path: impl AsRef) -> bool { .unwrap_or(false) } +/// Deduce the path to the root of the Enso repository. +/// +/// This function will traverse the filesystem upwards from the binary location until it finds a +/// directory that looks like the root of the Enso repository. #[instrument(ret, err)] pub fn deduce_repository_path() -> Result { - let candidate_paths = [ - std::env::current_dir().ok(), - std::env::current_dir().ok().and_then(|p| p.parent().map(ToOwned::to_owned)), - std::env::current_dir().ok().and_then(|p| p.parent().map(|p| p.join("enso5"))), - std::env::current_dir().ok().and_then(|p| p.parent().map(|p| p.join("enso"))), - ]; - for candidate in candidate_paths { - if let Some(path) = candidate && looks_like_enso_repository_root(&path) { - return Ok(path) - } + let mut path = ide_ci::env::current_exe()?; + while !looks_like_enso_repository_root(&path) { + ensure!(path.pop(), "Failed to deduce repository path."); } - bail!("Could not deduce repository path.") + Ok(path) } diff --git a/build/build/src/repo/cloud.rs b/build/build/src/repo/cloud.rs index 370554119d..400f81f193 100644 --- a/build/build/src/repo/cloud.rs +++ b/build/build/src/repo/cloud.rs @@ -1,11 +1,15 @@ +//! Code for dealing with the [Enso Cloud repository](https://github.com/enso-org/cloud-v2). + use crate::prelude::*; use ide_ci::github::RepoRef; +/// The cloud repository. pub const CLOUD_REPO: RepoRef = RepoRef { owner: "enso-org", name: "cloud-v2" }; +/// The workflow we need to invoke to build the backend image. pub const BUILD_IMAGE_WORKFLOW: &str = "build-image.yaml"; /// Build Image workflow input. Follows schema defined by @@ -34,7 +38,7 @@ pub async fn build_image_workflow_dispatch_input(octocrab: &Octocrab, version: & #[cfg(test)] mod tests { use super::*; - use crate::setup_octocrab; + use ide_ci::github::setup_octocrab; #[tokio::test] #[ignore] diff --git a/build/ci-gen/Cargo.toml b/build/ci-gen/Cargo.toml new file mode 100644 index 0000000000..f86117f022 --- /dev/null +++ b/build/ci-gen/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "enso-build-ci-gen" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +ide-ci = { path = "../ci_utils" } +enso-build = { path = "../build" } +enso-build-shader-tools = { path = "../shader-tools" } +serde_yaml = { workspace = true } +tokio = { workspace = true } diff --git a/build/ci-gen/src/main.rs b/build/ci-gen/src/main.rs new file mode 100644 index 0000000000..1222166c15 --- /dev/null +++ b/build/ci-gen/src/main.rs @@ -0,0 +1,46 @@ +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] + +use crate::prelude::*; + +use enso_build::paths::generated::RepoRootGithub; +use enso_build::repo::deduce_repository_path; +use ide_ci::actions::workflow::definition::WorkflowToWrite; + + + +pub mod prelude { + pub use enso_build::prelude::*; + pub use enso_build_shader_tools::prelude::*; +} + +/// Generate the comment that is at the top of each generated workflow file. +fn preamble(source: &str) -> String { + format!( + "# This file is auto-generated. Do not edit it manually!\n\ + # Edit the {source} module instead and run `cargo run --package {}`.", + env!("CARGO_PKG_NAME") + ) +} + +#[tokio::main] +async fn main() -> Result { + setup_logging()?; + let repo_root = deduce_repository_path()?; + let workflows_dir = RepoRootGithub::new_under(&repo_root).workflows; + let mut workflows = enso_build::ci_gen::generate(&workflows_dir)?; + workflows.push(enso_build_shader_tools::ci::generate_workflow(&workflows_dir.shader_tools_yml)); + + for WorkflowToWrite { source, path, workflow } in workflows { + let preamble = preamble(&source); + let yaml = serde_yaml::to_string(&workflow)?; + let contents = format!("{preamble}\n\n{yaml}"); + ide_ci::fs::tokio::write(path, contents).await?; + } + + warn!("Remember to run formatter on the generated files!"); + Ok(()) +} diff --git a/build/ci_utils/Cargo.toml b/build/ci_utils/Cargo.toml index 8f48544e0b..274e4a4139 100644 --- a/build/ci_utils/Cargo.toml +++ b/build/ci_utils/Cargo.toml @@ -44,9 +44,7 @@ mime = "0.3.16" multimap = "0.8.3" new_mime_guess = "4.0.0" nix = { workspace = true } -octocrab = { git = "https://github.com/enso-org/octocrab", default-features = false, features = [ - "rustls" -] } +octocrab = { workspace = true } paste = "1.0.7" path-absolutize = "3.0.11" pathdiff = "0.2.1" @@ -58,14 +56,14 @@ pretty_env_logger = "0.4.0" proc-macro2 = "1.0" quote = "1.0" rand = "0.8.4" -regex = "1.5.4" +regex = { workspace = true } reqwest = { version = "0.11.5", default-features = false, features = [ "stream" ] } semver = { version = "1.0.4", features = ["serde"] } serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.68" -serde_yaml = "0.9.10" +serde_yaml = { workspace = true } scopeguard = "1.1.0" sha2 = "0.10.2" shrinkwraprs = "0.3.0" diff --git a/build/ci_utils/src/actions/workflow/definition.rs b/build/ci_utils/src/actions/workflow/definition.rs index 19d91e8393..a65ba62d73 100644 --- a/build/ci_utils/src/actions/workflow/definition.rs +++ b/build/ci_utils/src/actions/workflow/definition.rs @@ -1,3 +1,5 @@ +//! Model of a workflow definition and related utilities. + use crate::prelude::*; use crate::env::accessor::RawVariable; @@ -6,9 +8,15 @@ use heck::ToKebabCase; use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::collections::BTreeSet; +use std::convert::identity; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; +/// Default timeout for a job. +/// +/// We use a very long timeout because we want to avoid cancelling jobs that are just slow. pub const DEFAULT_TIMEOUT_IN_MINUTES: u32 = 360; pub fn wrap_expression(expression: impl AsRef) -> String { @@ -174,7 +182,9 @@ impl Workflow { impl Workflow { pub fn add_job(&mut self, job: Job) -> String { let key = job.name.to_kebab_case(); - self.jobs.insert(key.clone(), job); + if self.jobs.insert(key.clone(), job).is_some() { + warn!("Job with name {key} already exists."); + } key } @@ -573,6 +583,10 @@ pub enum JobSecrets { Map(BTreeMap), } +/// Job is a top-level building block of a workflow. +/// +/// It is scheduled to run on a specific runner. A workflow can have multiple jobs, they will run in +/// parallel by default. #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Job { @@ -604,10 +618,37 @@ pub struct Job { } impl Job { - pub fn new(name: impl Into) -> Self { - Self { name: name.into(), timeout_minutes: Some(DEFAULT_TIMEOUT_IN_MINUTES), ..default() } + /// Create a new job definition. + pub fn new( + name: impl Into, + runs_on: impl IntoIterator>, + ) -> Self { + Self { + name: name.into(), + timeout_minutes: Some(DEFAULT_TIMEOUT_IN_MINUTES), + runs_on: runs_on.into_iter().map(Into::into).collect(), + ..Default::default() + } } + /// Add a step to this job, while exposing the step's outputs as job outputs. + pub fn add_step_with_output( + &mut self, + mut step: Step, + outputs: impl IntoIterator>, + ) { + static COUNTER: AtomicU64 = AtomicU64::new(0); + // A step must have an unique id if we want to access its output. + let id = + step.id.unwrap_or_else(|| format!("step_{}", COUNTER.fetch_add(1, Ordering::SeqCst))); + step.id = Some(id.clone()); + self.steps.push(step); + for output in outputs { + self.expose_output(&id, output); + } + } + + /// Expose a step's output as a job output. pub fn expose_output(&mut self, step_id: impl AsRef, output_name: impl Into) { let step = step_id.as_ref(); let output = output_name.into(); @@ -620,14 +661,19 @@ impl Job { } } + /// Define an environment variable for this job, it will be available to all steps. pub fn env(&mut self, name: impl Into, value: impl Into) { self.env.insert(name.into(), value.into()); } + /// Expose a secret as environment variable for this job. pub fn expose_secret_as(&mut self, secret: impl AsRef, given_name: impl Into) { self.env(given_name, format!("${{{{ secrets.{} }}}}", secret.as_ref())); } + /// Expose outputs of another job as environment variables in this job. + /// + /// This also adds a dependency on the other job. pub fn use_job_outputs(&mut self, job_id: impl Into, job: &Job) { let job_id = job_id.into(); for output_name in job.outputs.keys() { @@ -637,14 +683,19 @@ impl Job { self.needs(job_id); } + /// Add a dependency on another job. pub fn needs(&mut self, job_id: impl Into) { self.needs.insert(job_id.into()); } + /// Set an input for the invoked reusable workflow. + /// + /// This is only valid if the job uses a reusable workflow. pub fn with(&mut self, name: impl Into, value: impl Into) { self.with.insert(name.into(), value.into()); } + /// Like [`with`](Self::with), but self-consuming. pub fn with_with(mut self, name: impl Into, value: impl Into) -> Self { self.with(name, value); self @@ -817,6 +868,7 @@ pub enum CheckoutArgumentSubmodules { pub mod step { use super::*; + use crate::github; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -825,6 +877,11 @@ pub mod step { pub enum Argument { #[serde(rename_all = "kebab-case")] Checkout { + #[serde( + skip_serializing_if = "Option::is_none", + with = "crate::serde::via_string_opt" + )] + repository: Option, #[serde(skip_serializing_if = "Option::is_none")] clean: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -879,7 +936,7 @@ pub enum RunnerLabel { MatrixOs, } -pub fn checkout_repo_step() -> impl IntoIterator { +pub fn checkout_repo_step_customized(f: impl FnOnce(Step) -> Step) -> Vec { // This is a workaround for a bug in GH actions/checkout. If a submodule is added and removed, // it effectively breaks any future builds of this repository on a given self-hosted runner. // The workaround step below comes from: @@ -921,12 +978,19 @@ pub fn checkout_repo_step() -> impl IntoIterator { // shallow copy of the repo. uses: Some("actions/checkout@v2".into()), with: Some(step::Argument::Checkout { + repository: None, clean: Some(false), submodules: Some(CheckoutArgumentSubmodules::Recursive), }), ..default() }; - [submodules_workaround_win, submodules_workaround_linux, actual_checkout] + // Apply customization. + let actual_checkout = f(actual_checkout); + vec![submodules_workaround_win, submodules_workaround_linux, actual_checkout] +} + +pub fn checkout_repo_step() -> impl IntoIterator { + checkout_repo_step_customized(identity) } pub trait JobArchetype { @@ -957,3 +1021,14 @@ pub trait JobArchetype { } } } + +/// Describes the workflow to be stored on the disk. +#[derive(Clone, Debug)] +pub struct WorkflowToWrite { + /// The workflow to be stored. + pub workflow: Workflow, + /// The path where the workflow should be stored. + pub path: PathBuf, + /// Who generated this workflow. + pub source: String, +} diff --git a/build/ci_utils/src/cache/asset.rs b/build/ci_utils/src/cache/asset.rs index 7571af89a3..66939ce9be 100644 --- a/build/ci_utils/src/cache/asset.rs +++ b/build/ci_utils/src/cache/asset.rs @@ -1,9 +1,64 @@ // use crate::prelude::*; // -// use crate::models::config::RepoContext; -// use octocrab::repos::RepoHandler; -// use reqwest::RequestBuilder; +// use crate::cache::Cache; +// use crate::cache::Storable; +// use crate::github; +// use octocrab::models::AssetId; +// use reqwest::header::HeaderMap; +// use reqwest::header::HeaderValue; // +// #[derive(Clone, Debug, Serialize, Deserialize)] +// pub struct Key { +// pub repository: github::Repo, +// pub asset_id: AssetId, +// } +// +// #[derive(Clone, Debug)] +// pub struct Asset { +// pub key: Key, +// pub octocrab: Octocrab, +// } +// +// impl Storable for Asset { +// type Metadata = (); +// type Output = PathBuf; +// type Key = Key; +// +// fn generate(&self, cache: Cache, store: PathBuf) -> BoxFuture<'static, +// Result> { let this = self.clone(); +// async move { +// let Asset { octocrab, key: Key { asset_id, repository } } = this; +// let url = +// format!("https://api.github.com/repos/{repository}/releases/assets/{asset_id}"); +// let url = Url::parse(&url)?; +// let job = crate::cache::download::DownloadFile { +// client: octocrab.client.clone(), +// key: crate::cache::download::Key { +// url: url.clone(), +// additional_headers: HeaderMap::from_iter([( +// reqwest::header::ACCEPT, +// HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()), +// )]), +// }, +// }; +// cache.get(job).await.map(|_| ()) +// } +// .boxed() +// } +// +// fn adapt( +// &self, +// cache: PathBuf, +// _metadata: Self::Metadata, +// ) -> BoxFuture<'static, Result> { +// ready(Result::Ok(cache)).boxed() +// } +// +// fn key(&self) -> Self::Key { +// self.key.clone() +// } +// } + // pub struct DownloadAsset { // pub octocrab: Octocrab, // pub repo: RepoContext, diff --git a/build/ci_utils/src/cache/goodie.rs b/build/ci_utils/src/cache/goodie.rs index 7b651f1896..2a97e19d73 100644 --- a/build/ci_utils/src/cache/goodie.rs +++ b/build/ci_utils/src/cache/goodie.rs @@ -14,11 +14,18 @@ pub mod sbt; -/// Something that can be downloaded and, after that, enabled by modifying global state. +/// Something that can be obtained (with IO) and, after that, enabled by modifying global state. pub trait Goodie: Debug + Clone + Send + Sync + 'static { - fn url(&self) -> BoxFuture<'static, Result>; + /// Obtain and place this in the cache. + fn get(&self, cache: &Cache) -> BoxFuture<'static, Result>; + + /// Check whether this is already present. fn is_active(&self) -> BoxFuture<'static, Result>; + + /// Changes to the environment to activate this. fn activation_env_changes(&self, package_path: &Path) -> Result>; + + /// Apply the activation environment changes. fn activate(&self, package_path: &Path) -> Result { for modification in self.activation_env_changes(package_path)? { modification.apply()?; @@ -42,56 +49,38 @@ pub trait GoodieExt: Goodie { trace!("Skipping activation of {this:?} because it already present.",); Result::Ok(None) } else { - let package = this.download(&cache).await?; + let package = this.get(&cache).await?; this.activate(&package)?; Result::Ok(Some(package)) } } .boxed() } - - - fn package( - &self, - ) -> BoxFuture<'static, Result>> - { - let url_fut = self.url(); - async move { - let url = url_fut.await?; - let archive_source = cache::download::DownloadFile::new(url)?; - let path_to_extract = None; - Ok(cache::archive::ExtractedArchive { archive_source, path_to_extract }) - } - .boxed() - } - - fn download(&self, cache: &Cache) -> BoxFuture<'static, Result> { - let package = self.package(); - let cache = cache.clone(); - async move { cache.get(package.await?).await }.boxed() - } } impl GoodieExt for T {} -// -// /// Whoever owns a token, can assume that the Goodie is available. -// #[derive(Clone, Debug, Display)] -// pub struct Token(G); -// -// #[derive(Clone, Debug, Display)] -// pub struct PotentialFutureGoodie(Box BoxFuture<'static, Result>>>); -// -// impl PotentialFutureGoodie { -// pub fn new(f: F) -> Self -// where -// F: FnOnce() -> Fut + 'static, -// Fut: Future>> + Send + 'static, { -// Self(Box::new(move || f().boxed())) -// } -// } -// -// // pub type GoodieGenerator = -// // dyn FnOnce(Cache, G) -> BoxFuture<'static, Result>> + Send + Sync + 'static; -// // -// // pub type PotentialFutureGoodie = -// // dyn FnOnce(Cache) -> BoxFuture<'static, Result>> + Send + Sync + 'static; + + +pub fn download_url(url: Url, cache: &Cache) -> BoxFuture<'static, Result> { + download_try_url(Ok(url), cache) +} + + +pub fn download_try_url(url: Result, cache: &Cache) -> BoxFuture<'static, Result> { + download_try_future_url(ready(url), cache) +} + + +pub fn download_try_future_url( + url: impl Future> + Send + 'static, + cache: &Cache, +) -> BoxFuture<'static, Result> { + let cache = cache.clone(); + async move { + let url = url.await?; + let archive_source = cache::download::DownloadFile::new(url)?; + let package = cache::archive::ExtractedArchive { archive_source, path_to_extract: None }; + cache.get(package).await + } + .boxed() +} diff --git a/build/ci_utils/src/cache/goodie/binaryen.rs b/build/ci_utils/src/cache/goodie/binaryen.rs index c61b4b34ad..f75ae3af78 100644 --- a/build/ci_utils/src/cache/goodie/binaryen.rs +++ b/build/ci_utils/src/cache/goodie/binaryen.rs @@ -1,6 +1,7 @@ use crate::prelude::*; -use crate::cache; +use crate::cache::goodie; +use crate::cache::Cache; use crate::env::known::PATH; use crate::program::version::IsVersionPredicate; use crate::programs::wasm_opt; @@ -20,22 +21,24 @@ impl IsVersionPredicate for Binaryen { } } -impl Binaryen {} - -impl cache::Goodie for Binaryen { - fn url(&self) -> BoxFuture<'static, Result> { +impl Binaryen { + fn url(&self) -> Result { let version = format!("version_{}", self.version); - async move { - let target = match (TARGET_OS, TARGET_ARCH) { - (OS::Windows, Arch::X86_64) => "x86_64-windows", - (OS::Linux, Arch::X86_64) => "x86_64-linux", - (OS::MacOS, Arch::X86_64) => "x86_64-macos", - (OS::MacOS, Arch::AArch64) => "arm64-macos", - (os, arch) => bail!("Not supported arch/OS combination: {arch}-{os}."), - }; - let url = format!("https://github.com/WebAssembly/binaryen/releases/download/{version}/binaryen-{version}-{target}.tar.gz"); - url.parse2() - }.boxed() + let target = match (TARGET_OS, TARGET_ARCH) { + (OS::Windows, Arch::X86_64) => "x86_64-windows", + (OS::Linux, Arch::X86_64) => "x86_64-linux", + (OS::MacOS, Arch::X86_64) => "x86_64-macos", + (OS::MacOS, Arch::AArch64) => "arm64-macos", + (os, arch) => bail!("Not supported arch/OS combination: {arch}-{os}."), + }; + let url = format!("https://github.com/WebAssembly/binaryen/releases/download/{version}/binaryen-{version}-{target}.tar.gz"); + url.parse2() + } +} + +impl Goodie for Binaryen { + fn get(&self, cache: &Cache) -> BoxFuture<'static, Result> { + goodie::download_try_url(self.url(), cache) } fn is_active(&self) -> BoxFuture<'static, Result> { diff --git a/build/ci_utils/src/cache/goodie/graalvm.rs b/build/ci_utils/src/cache/goodie/graalvm.rs index 3658d783d9..72f72bb974 100644 --- a/build/ci_utils/src/cache/goodie/graalvm.rs +++ b/build/ci_utils/src/cache/goodie/graalvm.rs @@ -1,6 +1,8 @@ use crate::prelude::*; +use crate::cache::goodie; use crate::cache::goodie::Goodie; +use crate::cache::Cache; use crate::env::known::PATH; use crate::github::RepoRef; use crate::programs::java; @@ -45,18 +47,11 @@ pub struct GraalVM { } impl Goodie for GraalVM { - fn url(&self) -> BoxFuture<'static, Result> { - let platform_string = self.platform_string(); - let graal_version = self.graal_version.clone(); - let client = self.client.clone(); - async move { - let repo = CE_BUILDS_REPOSITORY.handle(&client); - let release = repo.find_release_by_text(&graal_version.to_string()).await?; - crate::github::find_asset_url_by_text(&release, &platform_string).cloned() - } - .boxed() + fn get(&self, cache: &Cache) -> BoxFuture<'static, Result> { + goodie::download_try_future_url(self.url(), cache) } + fn is_active(&self) -> BoxFuture<'static, Result> { let expected_graal_version = self.graal_version.clone(); let expected_java_language_version = self.java_version; @@ -90,6 +85,18 @@ impl Goodie for GraalVM { } impl GraalVM { + pub fn url(&self) -> BoxFuture<'static, Result> { + let platform_string = self.platform_string(); + let graal_version = self.graal_version.clone(); + let client = self.client.clone(); + async move { + let repo = CE_BUILDS_REPOSITORY.handle(&client); + let release = repo.find_release_by_text(&graal_version.to_string()).await?; + crate::github::find_asset_url_by_text(&release, &platform_string).cloned() + } + .boxed() + } + pub fn platform_string(&self) -> String { let Self { graal_version: _graal_version, java_version, arch, os, client: _client } = &self; let os_name = match *os { diff --git a/build/ci_utils/src/cache/goodie/sbt.rs b/build/ci_utils/src/cache/goodie/sbt.rs index badc2556e2..0bf375efb4 100644 --- a/build/ci_utils/src/cache/goodie/sbt.rs +++ b/build/ci_utils/src/cache/goodie/sbt.rs @@ -1,6 +1,7 @@ use crate::prelude::*; -use crate::cache; +use crate::cache::goodie; +use crate::cache::Cache; use crate::env::known::PATH; use crate::programs; @@ -15,9 +16,9 @@ crate::define_env_var! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] pub struct Sbt; -impl cache::Goodie for Sbt { - fn url(&self) -> BoxFuture<'static, Result> { - ready(Url::parse(DOWNLOAD_URL_TEXT).anyhow_err()).boxed() +impl Goodie for Sbt { + fn get(&self, cache: &Cache) -> BoxFuture<'static, Result> { + goodie::download_try_url(Url::from_str(DOWNLOAD_URL_TEXT), cache) } fn is_active(&self) -> BoxFuture<'static, Result> { diff --git a/build/ci_utils/src/env.rs b/build/ci_utils/src/env.rs index 607970064a..6f60f3a4f9 100644 --- a/build/ci_utils/src/env.rs +++ b/build/ci_utils/src/env.rs @@ -30,6 +30,11 @@ pub fn set_current_dir(path: impl AsRef) -> Result { .with_context(|| format!("Failed to set current directory to {}.", path.as_ref().display())) } +/// Like [`std::env::current_exe`], but with nicer error message. +pub fn current_exe() -> Result { + std::env::current_exe().context("Failed to get current executable path.") +} + /// Like [`std::env::set_var`], but with log. pub fn set_var, V: AsRef>(key: K, value: V) { debug!( @@ -63,6 +68,13 @@ pub fn remove_var>(key: K) { #[macro_export] macro_rules! define_env_var { () => {}; + ($(#[$attr:meta])* $name: ident, Vec; $($tail:tt)*) => { + #[allow(non_upper_case_globals)] + $(#[$attr])* + pub const $name: $crate::env::accessor::PathLike = + $crate::env::accessor::PathLike(stringify!($name)); + $crate::define_env_var!($($tail)*); + }; ($(#[$attr:meta])* $name: ident, PathBuf; $($tail:tt)*) => { #[allow(non_upper_case_globals)] $(#[$attr])* @@ -101,7 +113,7 @@ pub fn expect_var_os(name: impl AsRef) -> Result { .with_context(|| format!("Missing environment variable {}.", name.to_string_lossy())) } -pub fn prepend_to_path(path: impl Into) -> Result { +pub fn prepend_to_path(path: impl AsRef) -> Result { known::PATH.prepend(path) } diff --git a/build/ci_utils/src/env/accessor.rs b/build/ci_utils/src/env/accessor.rs index 27319db247..b90aa6c6f4 100644 --- a/build/ci_utils/src/env/accessor.rs +++ b/build/ci_utils/src/env/accessor.rs @@ -210,8 +210,9 @@ impl TypedVariable for PathLike { } impl PathLike { - pub fn prepend(&self, value: impl Into) -> Result { - let value = value.into(); + #[context("Failed to prepend path `{}` to `{}`.", value.as_ref().display(), self.name())] + pub fn prepend(&self, value: impl AsRef) -> Result { + let value = value.as_ref().to_path_buf(); trace!("Prepending {} to {}.", value.display(), self.name()); let mut paths = self.get()?; paths.insert(0, value); diff --git a/build/ci_utils/src/extensions/version.rs b/build/ci_utils/src/extensions/version.rs index f7c30d6071..979693ec27 100644 --- a/build/ci_utils/src/extensions/version.rs +++ b/build/ci_utils/src/extensions/version.rs @@ -4,13 +4,40 @@ use semver::Prerelease; +/// Extension methods for [`Version`]. pub trait VersionExt { + /// Get the version numbers, excluding the prerelease or build metadata. fn core(&self) -> (u64, u64, u64); + + /// Check if the version are the same while ignoring any prerelease or build metadata. fn same_core(&self, other: &Self) -> bool { self.core() == other.core() } + /// Get the identifiers (i.e. the dot-separated parts after the hyphen) of the prerelease. + /// + /// ``` + /// # use semver::Version; + /// # use ide_ci::extensions::version::VersionExt; + /// assert!(Version::parse("1.2.3").unwrap().identifiers().is_empty()); + /// assert_eq!(Version::parse("1.2.3-alpha").unwrap().identifiers(), vec!["alpha"]); + /// assert_eq!(Version::parse("1.2.3-alpha.1").unwrap().identifiers(), vec!["alpha", "1"]); + /// assert_eq!(Version::parse("1.2.3-alpha+build.1").unwrap().identifiers(), vec!["alpha"]); + /// ``` fn identifiers(&self) -> Vec<&str>; + + /// Generate next minor version for this major release. + /// + /// ``` + /// # use semver::Version; + /// # use ide_ci::extensions::version::VersionExt; + /// let version = Version::parse("1.2.3-dev").unwrap(); + /// assert_eq!(version.next_minor().to_string(), "1.3.0"); + /// + /// let version = Version::parse("2.2.0+fooo").unwrap(); + /// assert_eq!(version.next_minor().to_string(), "2.3.0"); + /// ``` + fn next_minor(&self) -> Self; } impl VersionExt for Version { @@ -20,6 +47,9 @@ impl VersionExt for Version { fn identifiers(&self) -> Vec<&str> { self.pre.identifiers() } + fn next_minor(&self) -> Self { + Version::new(self.major, self.minor + 1, 0) + } } pub trait PrereleaseExt { diff --git a/build/ci_utils/src/fs.rs b/build/ci_utils/src/fs.rs index 76959ed7ca..7202053643 100644 --- a/build/ci_utils/src/fs.rs +++ b/build/ci_utils/src/fs.rs @@ -1,8 +1,12 @@ +//! Wrappers around the `std::fs` module, which provide better error messages and avoid some +//! typical pitfalls. + use crate::prelude::*; use async_compression::tokio::bufread::GzipEncoder; use async_compression::Level; use fs_extra::dir::CopyOptions; +use fs_extra::error::ErrorKind; // ============== @@ -16,21 +20,22 @@ pub use enso_build_base::fs::*; +/// Copy source item (file or a directory) to a destination directory, preserving the filename. #[tracing::instrument(skip_all, fields( src = %source_file.as_ref().display(), dest = %dest_dir.as_ref().display()), err)] -pub fn copy_to(source_file: impl AsRef, dest_dir: impl AsRef) -> Result { +pub fn copy_to(source_file: impl AsRef, dest_dir: impl AsRef) -> Result { require_exist(&source_file)?; create_dir_if_missing(dest_dir.as_ref())?; debug!("Will copy {} to {}", source_file.as_ref().display(), dest_dir.as_ref().display()); let mut options = CopyOptions::new(); options.overwrite = true; - fs_extra::copy_items(&[source_file], dest_dir, &options)?; - Ok(()) + fs_extra::copy_items(&[&source_file], &dest_dir, &options).map_err(handle_fs_extra_error)?; + Ok(dest_dir.as_ref().join(source_file.as_ref().try_file_name()?)) } - +/// Copy the item (file or a directory) to a destination path. #[tracing::instrument(skip_all, fields( src = %source_file.as_ref().display(), dest = %destination_file.as_ref().display()), @@ -45,7 +50,8 @@ pub fn copy(source_file: impl AsRef, destination_file: impl AsRef) - let mut options = fs_extra::dir::CopyOptions::new(); options.overwrite = true; options.content_only = true; - fs_extra::dir::copy(source_file, destination_file, &options)?; + fs_extra::dir::copy(source_file, destination_file, &options) + .map_err(handle_fs_extra_error)?; } else { enso_build_base::fs::wrappers::copy(source_file, destination_file)?; } @@ -55,7 +61,12 @@ pub fn copy(source_file: impl AsRef, destination_file: impl AsRef) - Ok(()) } - +/// Mirrors the directory (like `rsync`). +/// +/// All files and directories from the source directory will be copied to the destination directory, +/// unless they are already present and have the same content. +/// Any files or directories that are present in the destination directory, but not in the source +/// directory, will be removed. pub async fn mirror_directory(source: impl AsRef, destination: impl AsRef) -> Result { create_dir_if_missing(destination.as_ref())?; @@ -79,6 +90,9 @@ pub async fn compressed_size(path: impl AsRef) -> Result crate::io::read_length(encoded_stream).await.map(into) } +/// Copy the file to the destination path, unless the file already exists and has the same content. +/// +/// If the directory is passed as the source, it will be copied recursively. #[tracing::instrument(skip_all, fields( src = %source.as_ref().display(), dest = %target.as_ref().display()), @@ -99,8 +113,26 @@ pub async fn copy_if_different(source: impl AsRef, target: impl AsRef {}", src.as_ref().display(), dst.as_ref().display())] pub fn symlink_auto(src: impl AsRef, dst: impl AsRef) -> Result { create_parent_dir_if_missing(&dst)?; symlink::symlink_auto(&src, &dst).anyhow_err() } + +/// `fs_extra`'s error type is not friendly to `anyhow`, so we need to convert it manually. +/// +/// Otherwise, we get just the message to look into the error kind, but the kind information is +/// lost. +pub fn handle_fs_extra_error(error: fs_extra::error::Error) -> anyhow::Error { + let message = error.to_string(); + match error.kind { + ErrorKind::Io(inner) => anyhow::Error::new(inner), + ErrorKind::StripPrefix(inner) => anyhow::Error::new(inner), + ErrorKind::OsString(inner) => anyhow::Error::msg(inner.to_string_lossy().to_string()), + _ => return error.into(), + } + .context(message) +} diff --git a/build/ci_utils/src/fs/tokio.rs b/build/ci_utils/src/fs/tokio.rs index 95c1972325..f415922725 100644 --- a/build/ci_utils/src/fs/tokio.rs +++ b/build/ci_utils/src/fs/tokio.rs @@ -1,3 +1,5 @@ +//! Asynchronous filesystem operations using tokio. + use crate::prelude::*; use tokio::fs::File; @@ -77,15 +79,6 @@ pub async fn remove_dir_if_exists(path: impl AsRef) -> Result { } } -pub async fn perhaps_remove_dir_if_exists(dry_run: bool, path: impl AsRef) -> Result { - if dry_run { - info!("Would remove directory {}.", path.as_ref().display()); - Ok(()) - } else { - remove_dir_if_exists(path).await - } -} - /// Recreate directory, so it exists and is empty. pub async fn reset_dir(path: impl AsRef) -> Result { let path = path.as_ref(); @@ -124,3 +117,90 @@ pub async fn append(path: impl AsRef, contents: impl AsRef<[u8]>) -> Resul .await .with_context(|| format!("Failed to write to file {}.", path.as_ref().display())) } + +/// Copy a file between directory subtrees, preserving the relative path. +/// +/// Source file must be within the source directory subtree. Path can be either relative or +/// absolute. +/// +/// Example: +/// ``` +/// use ide_ci::prelude::*; +/// +/// use ide_ci::fs::tokio::copy_between; +/// #[tokio::main] +/// async fn main() -> Result { +/// let tmp1 = tempfile::tempdir()?; +/// let relative_path = PathBuf::from_iter(["bin", "program"]); +/// let contents = "Hello, world!"; +/// ide_ci::fs::tokio::write(tmp1.path().join_iter(&relative_path), contents).await?; +/// let tmp2 = tempfile::tempdir()?; +/// copy_between(tmp1.path(), tmp2.path(), &relative_path).await?; +/// +/// let copied = +/// ide_ci::fs::tokio::read_to_string(tmp2.path().join_iter(&relative_path)).await?; +/// assert_eq!(contents, copied); +/// Ok(()) +/// } +/// ``` +pub async fn copy_between( + source_dir: impl AsRef, + destination_dir: impl AsRef, + source_file: impl AsRef, +) -> Result { + let source_file = source_file.as_ref(); + let source_file = if source_file.is_absolute() { + source_file.strip_prefix(source_dir.as_ref()).with_context(|| { + format!( + "Failed to strip prefix {} from {}.", + source_dir.as_ref().display(), + source_file.display() + ) + })? + } else { + source_file + }; + let source_path = source_dir.as_ref().join(source_file); + let destination_path = destination_dir.as_ref().join(source_file); + copy(&source_path, &destination_path) + .instrument(info_span!("copy_between", ?source_path, ?destination_path)) + .await?; + Ok(destination_path) +} + +/// Asynchronous version of [`crate::fs::copy`]. +pub async fn copy(source_file: impl AsRef, destination_file: impl AsRef) -> Result { + let source_file = source_file.as_ref().to_path_buf(); + let destination_file = destination_file.as_ref().to_path_buf(); + tokio::task::spawn_blocking(move || crate::fs::copy(&source_file, &destination_file)).await? +} + + +/// Remove a regular file. +/// +/// Does not fail if the file is not found. +#[context("Failed to remove file {}", path.as_ref().display())] +pub async fn remove_file_if_exists(path: impl AsRef) -> Result<()> { + let result = tokio::fs::remove_file(&path).await; + match result { + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), + result => result.anyhow_err(), + } +} + +/// Fail if the given path does not exist. +pub async fn require_exist(path: impl AsRef) -> Result { + if metadata(&path).await.is_ok() { + trace!("{} does exist.", path.as_ref().display()); + Ok(()) + } else { + bail!("{} does not exist.", path.as_ref().display()) + } +} + +/// Asynchronous version of [`crate::fs::copy_to`]. +pub async fn copy_to(source_file: impl AsRef, dest_dir: impl AsRef) -> Result { + let source_file = source_file.as_ref().to_path_buf(); + let dest_dir = dest_dir.as_ref().to_path_buf(); + tokio::task::spawn_blocking(move || crate::fs::copy_to(&source_file, &dest_dir)).await? +} diff --git a/build/ci_utils/src/github.rs b/build/ci_utils/src/github.rs index ad64a6e593..7f6c0e827d 100644 --- a/build/ci_utils/src/github.rs +++ b/build/ci_utils/src/github.rs @@ -1,11 +1,14 @@ use crate::prelude::*; +use crate::define_env_var; + use octocrab::models::repos::Asset; use octocrab::models::repos::Release; - -const MAX_PER_PAGE: u8 = 100; +// ============== +// === Export === +// ============== pub mod model; pub mod release; @@ -16,6 +19,78 @@ pub use repo::Repo; pub use repo::RepoRef; + +/// Maximum number of items per page in the GitHub API. +const MAX_PER_PAGE: u8 = 100; + +define_env_var! { + /// GitHub Personal Access Token, used for authentication in GutHub API. + /// + /// Can be [created using GitHub web UI](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). + GITHUB_TOKEN, String; +} + +/// Tries to retrieve the GitHub Personal Access Token from the environment. +pub fn retrieve_github_access_token() -> Result { + fn get_token_from_file() -> Result { + let path = + dirs::home_dir().context("Failed to locate home directory.")?.join("GITHUB_TOKEN"); + debug!("Looking for GitHub token in the file {}", path.display()); + let content = crate::fs::read_to_string(path)?; + Ok(content.trim().into()) + } + + GITHUB_TOKEN + .get() + .inspect(|_| debug!("Will use {GITHUB_TOKEN} environment variable.")) + .inspect_err(|e| debug!("Failed to retrieve GitHub authentication from environment: {e}")) + .or_else(|_| get_token_from_file()) +} + + +/// Prepare the octocrab (GitHub API client) using the authentication token from the environment. +#[context("Failed to setup GitHub API client.")] +pub async fn setup_octocrab() -> Result { + let builder = octocrab::OctocrabBuilder::new(); + let octocrab = if let Ok(access_token) = retrieve_github_access_token() { + let octocrab = builder.personal_token(access_token).build()?; + let username = octocrab + .current() + .user() + .await + .inspect_err(|e| warn!("Failed to retrieve GitHub username: {e}")) + .map_or_else(|_| "N/A".to_string(), |user| user.login); + info!("Using GitHub API with personal access token. Authenticated as {username}.",); + octocrab + } else { + info!("No GitHub Personal Access Token found. Will use anonymous API access."); + warn!( + "Anonymous GitHub API access is rate-limited. If you are experiencing issues, please \ + set the GITHUB_TOKEN environment variable." + ); + warn!( + "Additionally some APIs may not be available to anonymous users. This primarily \ + pertains the release-related APIs." + ); + builder.build()? + }; + + // LPrint rate limit. This both helps debugging related issues and allows to validate the + // GitHub access token. + octocrab + .ratelimit() + .get() + .await + .inspect(|rate| { + info!( + "GitHub API rate limit: {}/{}.", + rate.resources.core.used, rate.resources.core.limit + ) + }) + .context("Failed to get rate limit info. GitHub Personal Access Token might be invalid")?; + Ok(octocrab) +} + /// Goes over all the pages and returns result. /// /// We prefer taking a future page result rather than page itself to be able to easily wrap both @@ -29,6 +104,7 @@ pub async fn get_all( client.all_pages(first_page).await } +/// Utility functions for dealing with organization-specific GitHub API. #[async_trait] pub trait IsOrganization { /// Organization name. @@ -39,15 +115,18 @@ pub trait IsOrganization { &self, octocrab: &Octocrab, ) -> anyhow::Result { - let path = iformat!("/orgs/{self.name()}/actions/runners/registration-token"); + let name = self.name(); + let path = format!("/orgs/{name}/actions/runners/registration-token"); let url = octocrab.absolute_url(path)?; - octocrab.post(url, EMPTY_REQUEST_BODY).await.map_err(Into::into) + octocrab.post(url, EMPTY_REQUEST_BODY).await.with_context(|| { + format!("Failed to generate runner registration token for organization {name}.") + }) } /// The organization's URL. fn url(&self) -> Result { let url_text = iformat!("https://github.com/{self.name()}"); - Url::parse(&url_text).map_err(Into::into) + Url::from_str(&url_text) } } @@ -97,6 +176,7 @@ pub async fn latest_runner_url(octocrab: &Octocrab, os: OS) -> Result { find_asset_url_by_text(&latest_release, &platform_name).cloned() } +/// Download and extract latest GitHub Actions runner package for a given system. pub async fn fetch_runner(octocrab: &Octocrab, os: OS, output_dir: impl AsRef) -> Result { let url = latest_runner_url(octocrab, os).await?; crate::io::download_and_extract(url, output_dir).await diff --git a/build/ci_utils/src/github/release.rs b/build/ci_utils/src/github/release.rs index 75d74de692..351b8d8304 100644 --- a/build/ci_utils/src/github/release.rs +++ b/build/ci_utils/src/github/release.rs @@ -70,7 +70,7 @@ pub trait IsReleaseExt: IsRelease + Sync { /// Upload a new asset to the release from a given file. /// /// The filename will be used to name the asset and deduce MIME content type. - #[instrument(skip_all, fields(source = %path.as_ref().display()))] + #[instrument(skip_all, fields(source = %path.as_ref().display()), err)] async fn upload_asset_file(&self, path: impl AsRef + Send) -> Result { let error_msg = format!("Failed to upload an asset from the file under {}.", path.as_ref().display()); @@ -121,6 +121,17 @@ pub trait IsReleaseExt: IsRelease + Sync { .await .anyhow_err() } + + async fn publish(&self) -> Result { + self.octocrab() + .repos(self.repo().owner(), self.repo().name()) + .releases() + .update(self.id().0) + .draft(false) + .send() + .await + .with_context(|| format!("Failed to publish the release {}.", self.id())) + } } impl IsReleaseExt for T where T: IsRelease + Sync {} @@ -128,7 +139,7 @@ impl IsReleaseExt for T where T: IsRelease + Sync {} /// A release on GitHub. #[derive(Clone, Derivative)] #[derivative(Debug)] -pub struct ReleaseHandle { +pub struct Handle { #[derivative(Debug(format_with = "std::fmt::Display::fmt"))] pub repo: Repo, pub id: ReleaseId, @@ -136,7 +147,7 @@ pub struct ReleaseHandle { pub octocrab: Octocrab, } -impl IsRelease for ReleaseHandle { +impl IsRelease for Handle { fn id(&self) -> ReleaseId { self.id } @@ -150,7 +161,7 @@ impl IsRelease for ReleaseHandle { } } -impl ReleaseHandle { +impl Handle { pub fn new(octocrab: &Octocrab, repo: impl Into, id: ReleaseId) -> Self { let repo = repo.into(); Self { repo, id, octocrab: octocrab.clone() } diff --git a/build/ci_utils/src/github/repo.rs b/build/ci_utils/src/github/repo.rs index 8a0cc7e96e..d8e4d246ae 100644 --- a/build/ci_utils/src/github/repo.rs +++ b/build/ci_utils/src/github/repo.rs @@ -1,3 +1,5 @@ +//! Utilities to deal with GitHub repositories, particularly the ones with the fonts. + use crate::prelude::*; use crate::cache::download::DownloadFile; @@ -22,11 +24,14 @@ use reqwest::Response; /// Owned data denoting a specific GitHub repository. +/// +/// See also [`RepoRef`] for a non-owning equivalent. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, derive_more::Display)] #[display(fmt = "{}/{}", owner, name)] pub struct Repo { /// Owner - an organization's or user's name. pub owner: String, + /// Repository name. pub name: String, } @@ -40,7 +45,7 @@ impl IsRepo for Repo { } } -/// Parse from strings in format "owner/name". Opposite of `Display`. +/// Parse from strings in format "owner/name". Opposite of [`Display`]. impl std::str::FromStr for Repo { type Err = anyhow::Error; @@ -55,7 +60,14 @@ impl<'a> From> for Repo { } } +impl<'a> From<&'a Repo> for RepoRef<'a> { + fn from(value: &'a Repo) -> Self { + RepoRef { owner: &value.owner, name: &value.name } + } +} + impl Repo { + #[allow(missing_docs)] pub fn new(owner: impl Into, name: impl Into) -> Self { Self { owner: owner.into(), name: name.into() } } @@ -70,6 +82,7 @@ impl Repo { pub struct RepoRef<'a> { /// Owner - an organization's or user's name. pub owner: &'a str, + /// Repository name. pub name: &'a str, } @@ -84,6 +97,7 @@ impl<'a> IsRepo for RepoRef<'a> { } impl<'a> RepoRef<'a> { + #[allow(missing_docs)] pub const fn new(owner: &'a T1, name: &'a T2) -> Self where T1: ~const AsRef + ?Sized, @@ -92,7 +106,7 @@ impl<'a> RepoRef<'a> { } } -/// Note that we chose to implemend `TryFrom` rather than `FromStr` for `RepoRef` because +/// Note that we chose to implement `TryFrom` rather than `FromStr` for `RepoRef` because /// `FromStr` requires the parsed value to be owned (or at least lifetime-independent from input), /// which is not the case for `RepoRef`. impl<'a> TryFrom<&'a str> for RepoRef<'a> { @@ -109,28 +123,51 @@ impl<'a> TryFrom<&'a str> for RepoRef<'a> { /// Any entity that uniquely identifies a GitHub-hosted repository. #[async_trait] pub trait IsRepo: Display { + /// Owner - an organization's or user's name. fn owner(&self) -> &str; + + /// Repository name. fn name(&self) -> &str; /// The repository's URL. + /// + /// ``` + /// # use ide_ci::github::Repo; + /// # use ide_ci::prelude::IsRepo; + /// let repo = Repo::new("enso-org", "enso"); + /// assert_eq!(repo.url().unwrap().to_string(), "https://github.com/enso-org/enso/"); + /// ``` fn url(&self) -> Result { - let url_text = iformat!("https://github.com/{self.owner()}/{self.name()}"); + // Note the trailing `/`. It allows us to join further paths to the URL using Url::join. + let url_text = iformat!("https://github.com/{self.owner()}/{self.name()}/"); Url::parse(&url_text) - .context(format!("Failed to generate an URL for the {self} repository.")) + .with_context(|| format!("Failed to parse URL from string '{url_text}'.")) + .with_context(|| format!("Failed to generate URL for the repository {self}.")) } + /// Add GitHub API client to obtain the [`Handle`] to this repository. fn handle(&self, octocrab: &Octocrab) -> Handle where Self: Clone + Sized { Handle { repo: self.clone(), octocrab: octocrab.clone() } } + + /// Add GitHub API client to obtain the [`Handle`] to this repository. + fn into_handle(self, octocrab: &Octocrab) -> Handle + where Self: Sized { + Handle { octocrab: octocrab.clone(), repo: self } + } } /// A handle to a specific GitHub repository. /// /// It includes a client (so also an authentication token) and a repository. -#[derive(Debug, Clone)] +#[derive(Derivative, Clone)] +#[derivative(Debug)] pub struct Handle { + /// Octocrab client (includes authentication token). + #[derivative(Debug = "ignore")] pub octocrab: Octocrab, + /// Repository designation. pub repo: Repo, } @@ -161,41 +198,54 @@ impl Handle { let path = iformat!("/repos/{self.owner()}/{self.name()}/actions/runners/registration-token"); let url = self.octocrab.absolute_url(path)?; - self.octocrab.post(url, EMPTY_REQUEST_BODY).await.context(format!( - "Failed to generate a runner registration token for the {self} repository." - )) + self.octocrab.post(url, EMPTY_REQUEST_BODY).await.with_context(|| { + format!("Failed to generate a runner registration token for the {self} repository.") + }) } + /// Get the [RepoHandler](octocrab::repos::RepoHandler), which is octocrab's entry point for + /// most of the repository-related operations. pub fn repos(&self) -> octocrab::repos::RepoHandler { self.octocrab.repos(self.owner(), self.name()) } + /// List all the releases of this repository. + /// + /// While this behavior is not documented anywhere, it seems that the GitHub API returns the + /// releases in the reverse chronological order (i.e. the newest release first). pub async fn all_releases(&self) -> Result> { github::get_all( &self.octocrab, self.repos().releases().list().per_page(MAX_PER_PAGE).send(), ) .await - .context(format!("Failed to list all releases in the {self} repository.")) + .with_context(|| format!("Failed to list all releases in the {self} repository.")) } + /// Get the latest release of this repository. + /// + /// The latest release is the most recent non-prerelease, non-draft release, sorted by the + /// `created_at` attribute. The `created_at` attribute is the date of the commit used for the + /// release, and not the date when the release was drafted or published. pub async fn latest_release(&self) -> Result { self.repos() .releases() .get_latest() .await - .context(format!("Failed to get the latest release in the {self} repository.")) + .with_context(|| format!("Failed to get the latest release in the {self} repository.")) } + /// Get the information about release with the given id. pub async fn find_release_by_id(&self, release_id: ReleaseId) -> Result { let repo_handler = self.repos(); let releases_handler = repo_handler.releases(); releases_handler .get_by_id(release_id) .await - .context(format!("Failed to find release by id `{release_id}` in `{self}`.")) + .with_context(|| format!("Failed to find release by id `{release_id}` in `{self}`.")) } + /// Get the latest release that satisfies the given predicate. #[tracing::instrument(fields(%self), skip(predicate), err)] pub async fn find_release_if(&self, predicate: impl Fn(&Release) -> bool) -> Result { let releases = self.all_releases().await?; @@ -203,6 +253,7 @@ impl Handle { release.context("Failed to find a release that satisfies the predicate.") } + /// Get the latest release that contains the given substring in its tag name. #[tracing::instrument(fields(%self, %text), err)] pub async fn find_release_by_text(&self, text: &str) -> anyhow::Result { self.find_release_if(|release| release.tag_name.contains(text)) @@ -211,6 +262,7 @@ impl Handle { .with_context(|| format!("No release with tag matching `{text}` in {self}.")) } + /// Get the release with the given tag name. #[tracing::instrument(fields(%self, %text), err)] pub async fn find_release_by_tag(&self, text: &str) -> anyhow::Result { self.find_release_if(|release| release.tag_name == text) @@ -219,10 +271,13 @@ impl Handle { .with_context(|| format!("No release with tag equal to `{text}` in {self}.")) } + /// Get a single [reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References) from + /// the repository's Git database. pub async fn get_ref(&self, r#ref: &Reference) -> Result { self.repos().get_ref(r#ref).await.context(format!("Failed to get ref `{ref}` in {self}.")) } + /// Lookup artifact by name in a GitHub Actions workflow run. #[tracing::instrument(fields(%self, %run_id, %name), err, ret)] pub async fn find_artifact_by_name( &self, @@ -236,24 +291,33 @@ impl Handle { .per_page(100) .send() .await - .context(format!("Failed to list artifacts of run {run_id} in {self}."))? + .with_context(|| format!("Failed to list artifacts of run {run_id} in {self}."))? .value - .context("Failed to find any artifacts.")?; + .with_context(|| { + format!("Failed to find any artifacts in the run {run_id} in {self}.") + })?; artifacts .into_iter() .find(|artifact| artifact.name == name) - .context(format!("Failed to find artifact by name '{name}'.")) + .with_context(|| format!("Failed to find artifact by name '{name}'.")) } + /// Download artifact by id in a GitHub Actions workflow run. + /// + /// The artifact is always packed into a zip archive. This method returns its binary contents. pub async fn download_artifact(&self, artifact_id: ArtifactId) -> Result { + // TODO: [mwu] Unfortunately the octocrab API does not support streaming the artifact, + // so we have to download it into memory first. self.octocrab .actions() .download_artifact(self.owner(), self.name(), artifact_id, ArchiveFormat::Zip) .await - .context(format!("Failed to download artifact with ID={artifact_id}.")) + .with_context(|| format!("Failed to download artifact with ID={artifact_id}.")) } + /// Download artifact by id in a GitHub Actions workflow run and extract top-level zip archive + /// (the one implicitly introduced by GitHub) into the given output directory. pub async fn download_and_unpack_artifact( &self, artifact_id: ArtifactId, @@ -264,11 +328,15 @@ impl Handle { Ok(()) } + /// Get information about a release asset with a given id. #[tracing::instrument(name="Get the asset information.", fields(self=%self), err)] pub async fn asset(&self, asset_id: AssetId) -> Result { - self.repos().releases().get_asset(asset_id).await.anyhow_err() + self.repos().releases().get_asset(asset_id).await.with_context(|| { + format!("Failed to get the asset information for asset with ID={asset_id} in {self}.") + }) } + /// Generate cacheable action that downloads asset with a given id. pub fn download_asset_job(&self, asset_id: AssetId) -> DownloadFile { let path = iformat!("/repos/{self.owner()}/{self.name()}/releases/assets/{asset_id}"); // Unwrap will work, because we are appending relative URL constant. @@ -285,11 +353,13 @@ impl Handle { } } + /// Make an HTTP request to download a release asset with a given id. #[tracing::instrument(name="Download the asset.", fields(self=%self), err)] pub async fn download_asset(&self, asset_id: AssetId) -> Result { self.download_asset_job(asset_id).send_request().await } + /// Download a release asset with a given id to a file. #[tracing::instrument(name="Download the asset to a file.", skip(output_path), fields(self=%self, dest=%output_path.as_ref().display()), err)] pub async fn download_asset_as( &self, @@ -300,6 +370,9 @@ impl Handle { crate::io::web::stream_response_to_file(response, &output_path).await } + /// Download a release asset with a given id to a file under the given directory. + /// + /// The file name is taken from the asset's name. #[tracing::instrument(name="Download the asset to a directory.", skip(output_dir, asset), fields(self=%self, dest=%output_dir.as_ref().display(), id = %asset.id), @@ -341,4 +414,10 @@ impl Handle { let default_branch = self.default_branch().await?; crate::github::workflow::dispatch(self, workflow_id, default_branch, inputs).await } + + /// Get a handle for dealing with a release with a given id. + pub fn release_handle(&self, id: ReleaseId) -> crate::github::release::Handle { + let repo = Repo::new(self.owner(), self.name()); + crate::github::release::Handle::new(&self.octocrab, repo, id) + } } diff --git a/build/ci_utils/src/goodie.rs b/build/ci_utils/src/goodie.rs deleted file mode 100644 index 56f5620299..0000000000 --- a/build/ci_utils/src/goodie.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::prelude::*; - -use crate::fs::create_dir_if_missing; - - - -#[async_trait] -pub trait Goodie { - // Assumed to be unique among types. - // TODO requirement should be lifted, consider something safer - const NAME: &'static str; - type Instance: Instance + Sized; - - async fn is_already_available(&self) -> anyhow::Result; - async fn lookup(&self, database: &GoodieDatabase) -> Result; - async fn install(&self, database: &GoodieDatabase) -> Result; -} - -pub trait Instance { - fn add_to_environment(&self) -> anyhow::Result<()>; -} - - -#[derive(Clone, Debug)] -pub struct GoodieDatabase { - pub root_directory: PathBuf, -} -impl GoodieDatabase { - pub fn new() -> anyhow::Result { - let home = dirs::home_dir().ok_or_else(|| anyhow!("Cannot figure out home directory."))?; - let path = home.join(".enso-ci"); - create_dir_if_missing(&path)?; - Ok(GoodieDatabase { root_directory: path }) - } - - pub async fn require(&self, goodie: &impl Goodie) -> Result { - if goodie.is_already_available().await? { - Ok(()) - } else if let Ok(instance) = goodie.lookup(self).await { - instance.add_to_environment() - } else { - let instance = goodie.install(self).await?; - instance.add_to_environment() - } - } - - pub fn find_dir(&self, directory_name: impl AsRef) -> Result { - let expected_dir_name = directory_name.as_ref(); - for entry in crate::fs::read_dir(&self.root_directory)? { - let entry = entry?; - if entry.file_type()?.is_dir() && entry.path().file_name().contains(&expected_dir_name) - { - return Ok(entry.path()); - } - } - bail!("no directory by name {} in the database.", expected_dir_name.display()) - } -} diff --git a/build/ci_utils/src/goodies.rs b/build/ci_utils/src/goodies.rs index 692a306145..3a50fc5063 100644 --- a/build/ci_utils/src/goodies.rs +++ b/build/ci_utils/src/goodies.rs @@ -1,2 +1,8 @@ -// pub mod musl; -// pub mod sbt; +//! Top-level module for [goodie](`crate::Goodie`) implementations. + + +// ============== +// === Export === +// ============== + +pub mod shader_tools; diff --git a/build/ci_utils/src/goodies/shader_tools.rs b/build/ci_utils/src/goodies/shader_tools.rs new file mode 100644 index 0000000000..391d6509f9 --- /dev/null +++ b/build/ci_utils/src/goodies/shader_tools.rs @@ -0,0 +1,80 @@ +//! Shader Tools is our collection of tools for working with shaders. +//! +//! The included programs are: +//! * [glslc](Glslc); +//! * [spirv-opt](SpirvOpt); +//! * [spirv-cross](SpirvCross). +//! +//! This module only deals with downloading and activating the tools. The code for building and +//! uploading the tools package is in the `enso-build-shader-tools` crate. + +use crate::prelude::*; + +use crate::cache::goodie; +use crate::cache::Cache; +use crate::cache::Goodie; +use crate::env::known::PATH; +use crate::github::RepoRef; +use crate::programs::shaderc::Glslc; +use crate::programs::shaderc::SpirvOpt; +use crate::programs::spirv_cross::SpirvCross; + + + +// ================= +// === Constants === +// ================= + +/// Repository where we store releases of the shader tools. +pub const SHADER_TOOLS_REPO: RepoRef = RepoRef { owner: "enso-org", name: "shader-tools" }; + +/// Version of the shader tools package that we download. +pub const VERSION: Version = Version::new(0, 1, 0); + + +// ========================= +// === Asset description === +// ========================= + +pub fn asset_name(os: OS) -> String { + // At the moment we don't have non-x64 binaries, so we can hardcode the architecture. + let arch = Arch::X86_64; + format!("shader-tools-{os}-{arch}.tar.gz") +} + + +// ========================= +// === Goodie definition === +// ========================= + +#[derive(Clone, Copy, Debug, Default)] +pub struct ShaderTools; + +impl Goodie for ShaderTools { + fn get(&self, cache: &Cache) -> BoxFuture<'static, Result> { + let url = SHADER_TOOLS_REPO.url().and_then(|url_base| { + let asset = asset_name(TARGET_OS); + let suffix = format!("releases/download/{VERSION}/{asset}"); + url_base + .join(&suffix) + .with_context(|| "Failed to append suffix {suffix} to URL {url_base}") + }); + goodie::download_try_url(url, cache) + } + + fn is_active(&self) -> BoxFuture<'static, Result> { + async move { + let glslc = Glslc.lookup(); + let spirv_cross = SpirvCross.lookup(); + let spirv_opt = SpirvOpt.lookup(); + Ok(glslc.is_ok() && spirv_cross.is_ok() && spirv_opt.is_ok()) + } + .boxed() + } + + fn activation_env_changes(&self, package_path: &Path) -> Result> { + let path = package_path.join_iter(["bin"]); + let path = crate::env::Modification::prepend_path(&PATH, path); + Ok(vec![path]) + } +} diff --git a/build/ci_utils/src/io/web.rs b/build/ci_utils/src/io/web.rs index 9e90b855ae..11a45df0b4 100644 --- a/build/ci_utils/src/io/web.rs +++ b/build/ci_utils/src/io/web.rs @@ -1,6 +1,7 @@ use crate::prelude::*; use crate::fs::tokio::copy_to_file; +use crate::fs::tokio::create_parent_dir_if_missing; use anyhow::Context; use reqwest::Client; @@ -97,6 +98,8 @@ pub async fn stream_to_file( stream: impl Stream>, output_path: impl AsRef, ) -> Result { + debug!("Streaming download to file {}. ", output_path.as_ref().display()); + create_parent_dir_if_missing(&output_path).await?; let output = tokio::fs::OpenOptions::new().write(true).create(true).open(&output_path).await?; stream .map_err(anyhow::Error::from) diff --git a/build/ci_utils/src/io/web/client.rs b/build/ci_utils/src/io/web/client.rs index 0197d4b9c3..b66f8f3ef5 100644 --- a/build/ci_utils/src/io/web/client.rs +++ b/build/ci_utils/src/io/web/client.rs @@ -21,6 +21,8 @@ pub async fn download( client: &Client, url: impl IntoUrl, ) -> Result>> { + let url = url.into_url()?; + debug!("Downloading {url}."); Ok(client.get(url).send().await?.error_for_status()?.bytes_stream()) } diff --git a/build/ci_utils/src/lib.rs b/build/ci_utils/src/lib.rs index a10eac30ae..b32bf7951f 100644 --- a/build/ci_utils/src/lib.rs +++ b/build/ci_utils/src/lib.rs @@ -1,4 +1,5 @@ // === Features === +#![feature(try_blocks)] #![feature(result_flattening)] #![feature(const_fmt_arguments_new)] #![feature(hash_set_entry)] @@ -53,7 +54,6 @@ pub mod fs; pub mod future; pub mod github; pub mod global; -pub mod goodie; pub mod goodies; pub mod io; pub mod log; @@ -96,10 +96,9 @@ pub mod prelude { pub use crate::EMPTY_REQUEST_BODY; - pub use crate::extensions::output::OutputExt as _; + pub use crate::cache::goodie::Goodie; pub use crate::github::release::IsRelease; pub use crate::github::repo::IsRepo; - pub use crate::goodie::Goodie; pub use crate::log::setup_logging; pub use crate::os::target::TARGET_ARCH; pub use crate::os::target::TARGET_OS; @@ -110,12 +109,12 @@ pub mod prelude { pub use crate::program::Program; pub use crate::program::Shell; - pub use crate::cache::goodie::GoodieExt as _; pub use crate::env::accessor::RawVariable as _; pub use crate::env::accessor::TypedVariable as _; pub use crate::extensions::clap::ArgExt as _; pub use crate::extensions::command::CommandExt as _; + pub use crate::extensions::output::OutputExt as _; pub use crate::extensions::version::PrereleaseExt as _; pub use crate::extensions::version::VersionExt as _; pub use crate::github::release::IsReleaseExt as _; diff --git a/build/ci_utils/src/program/command.rs b/build/ci_utils/src/program/command.rs index 9dc39b48e3..d93a07738a 100644 --- a/build/ci_utils/src/program/command.rs +++ b/build/ci_utils/src/program/command.rs @@ -451,7 +451,7 @@ pub fn spawn_log_processor( ) -> JoinHandle { tokio::task::spawn( async move { - info!("{prefix} "); + trace!("{prefix} "); let bufread = BufReader::new(out); let mut lines = bufread.split(b'\n'); while let Some(line_bytes) = lines.next_segment().await? { @@ -470,7 +470,7 @@ pub fn spawn_log_processor( } } } - info!("{prefix} "); + trace!("{prefix} "); Result::Ok(()) } .inspect_err(|e| error!("Fatal error while processing process output: {e}")), diff --git a/build/ci_utils/src/programs.rs b/build/ci_utils/src/programs.rs index e38129fefb..d44471bb67 100644 --- a/build/ci_utils/src/programs.rs +++ b/build/ci_utils/src/programs.rs @@ -6,6 +6,7 @@ use crate::prelude::*; // ============== pub mod cargo; +pub mod cmake; pub mod cmd; pub mod conda; pub mod docker; @@ -25,6 +26,9 @@ pub mod rustup; pub mod sbt; pub mod seven_zip; pub mod sh; +pub mod shaderc; +pub mod spirv_cross; +pub mod strip; pub mod tar; pub mod vs; pub mod vswhere; @@ -32,6 +36,7 @@ pub mod wasm_opt; pub mod wasm_pack; pub use cargo::Cargo; +pub use cmake::CMake; pub use cmd::Cmd; pub use conda::Conda; pub use docker::Docker; @@ -42,8 +47,10 @@ pub use java::Java; pub use javac::Javac; pub use node::Node; pub use node::Npm; +pub use npx::Npx; pub use pwsh::PwSh; pub use sbt::Sbt; pub use seven_zip::SevenZip; pub use sh::Bash; +pub use strip::Strip; pub use wasm_pack::WasmPack; diff --git a/build/ci_utils/src/programs/cmake.rs b/build/ci_utils/src/programs/cmake.rs new file mode 100644 index 0000000000..a76648fb11 --- /dev/null +++ b/build/ci_utils/src/programs/cmake.rs @@ -0,0 +1,187 @@ +//! Wrappers for [CMake program](https://cmake.org/) and its commands. + +use crate::prelude::*; + +use crate::program::command::Manipulator; + + + +// ===================== +// === Configuration === +// ===================== + +/// Standard build configurations defined by CMake. +/// +/// See [CMake documentation](https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#default-and-custom-configurations) +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Display, Eq, Hash, PartialEq)] +pub enum Configuration { + /// Non-optimized, debuggable binary. + Debug, + /// Optimized binary. + Release, + /// Add compiler flags for generating debug information (the -g flag for GCC / clang), and will + /// result in debuggable, yet much larger binaries. + RelWithDebInfo, + /// Add compiler flags for generating more compact binaries (the -Os flag for GCC / clang), + /// possibly on the expense of program speed. + MinSizeRel, +} + + +// =================== +// === CLI Options === +// =================== + +/// Define build type for a single configuration generator. +/// +/// See . +pub fn build_type(config: Configuration) -> SetVariable { + SetVariable::string("CMAKE_BUILD_TYPE", config.to_string()) +} + +/// Option that can be passed to `cmake --build` command. +#[derive(Clone, Copy, Debug)] +pub enum BuildOption { + /// The maximum number of concurrent processes to use when building. If value is omitted the + /// native build tool's default number is used. + Parallel(Option), + /// For multi-configuration tools, choose configuration. + /// + /// Single configuration tools will ignore this option, instead [`build_type`] should be used + /// during the generation phase. + Configuration(Configuration), +} + +impl BuildOption { + /// Enable default level of parallelism. + pub fn parallel() -> Self { + Self::Parallel(None) + } +} + +impl Manipulator for BuildOption { + fn apply(&self, command: &mut C) { + match self { + BuildOption::Parallel(jobs) => { + command.arg("--parallel"); + if let Some(jobs) = jobs { + command.arg(jobs.to_string()); + } + } + BuildOption::Configuration(config) => { + command.arg("--config"); + command.arg(config.to_string()); + } + } + } +} + +/// Options for `cmake --install` family of commands. +#[derive(Clone, Debug)] +pub enum InstallOption { + /// Install to the given directory. + Prefix(PathBuf), +} + +impl Manipulator for InstallOption { + fn apply(&self, command: &mut C) { + match self { + InstallOption::Prefix(path) => { + command.arg("--prefix"); + command.arg(path); + } + } + } +} + +/// Defines the given variable in the CMake cache. +#[derive(Clone, Debug)] +pub struct SetVariable { + /// Variable name. + pub variable: String, + /// Variable value. + value: String, +} + +impl SetVariable { + fn new(variable: impl Into, value: impl Into) -> Self { + let variable = variable.into(); + let value = value.into(); + Self { variable, value } + } + + /// Set given boolean option variable (`BOOL` type). + pub fn option(name: impl Into, value: bool) -> Self { + Self::new(name, if value { "ON" } else { "OFF" }) + } + + /// Set given file path variable (`FILEPATH` type). + pub fn file_path(name: impl Into, value: impl AsRef) -> Self { + Self::new(name, value.as_ref().as_str()) + } + + /// Set directory path variable (`PATH` type). + pub fn directory_path(name: impl Into, value: impl AsRef) -> Self { + Self::new(name, value.as_ref().as_str()) + } + + /// Set given string variable (`STRING` type). + pub fn string(name: impl Into, value: impl Into) -> Self { + Self::new(name, value) + } +} + +impl Manipulator for SetVariable { + fn apply(&self, command: &mut C) { + command.arg("-D").arg(format!("{}={}", self.variable, self.value)); + } +} + +// =============== +// === Program === +// =============== + +/// The [CMake program](https://cmake.org/). +/// +/// See [the official CLI documentation](https://cmake.org/cmake/help/latest/manual/cmake.1.html). +#[derive(Clone, Debug, Copy)] +pub struct CMake; + +impl Program for CMake { + fn executable_name(&self) -> &str { + "cmake" + } +} + +// =========================== +// === Helper Entry Points === +// =========================== + +/// Generate build files for the given project. +#[context("Failed to generate build files for {}.", source_dir.as_ref().display())] +pub fn generate(source_dir: impl AsRef, build_dir: impl AsRef) -> Result { + Ok(CMake.cmd()?.with_arg(source_dir.as_ref()).with_current_dir(build_dir.as_ref())) +} + +/// Build the project. The build_dir must be the same as the one used in the [generation +/// step](generate). +pub fn build(build_dir: impl AsRef) -> Result { + Ok(CMake + .cmd()? + .with_arg("--build") + .with_arg(".") + .with_applied(&BuildOption::parallel()) + .with_current_dir(&build_dir)) +} + +/// Install the project. The build_dir must be the same as the one used in the [generation +/// step](generate), and [`build`] should have been called before. +pub fn install(build_dir: impl AsRef, prefix_dir: impl AsRef) -> Result { + Ok(CMake + .cmd()? + .with_arg("--install") + .with_arg(".") + .with_applied(&InstallOption::Prefix(prefix_dir.as_ref().to_path_buf())) + .with_current_dir(&build_dir)) +} diff --git a/build/ci_utils/src/programs/git.rs b/build/ci_utils/src/programs/git.rs index 9b646f0230..20e9881986 100644 --- a/build/ci_utils/src/programs/git.rs +++ b/build/ci_utils/src/programs/git.rs @@ -54,6 +54,15 @@ impl Git { repository_root: path.as_ref().to_path_buf(), }) } + + /// Clone a repository into a new directory. + #[context("Failed to clone git repository {} into {}.", url.as_str(), path.as_ref().display())] + pub async fn clone(&self, path: impl AsRef, url: &Url) -> Result { + let path = path.as_ref(); + crate::fs::tokio::create_dir_if_missing(path).await?; + self.cmd()?.arg(Command::Clone).arg(url.as_str()).arg(path).run_ok().await?; + Context::new(path).await + } } /// The wrapper over `Git` program invocation context. @@ -85,10 +94,7 @@ impl Context { /// /// The caller is responsible for ensuring that the `working_dir` is a subdirectory of the /// `repository_root`. - pub async fn new_unchecked( - repository_root: impl AsRef, - working_dir: impl AsRef, - ) -> Self { + pub fn new_unchecked(repository_root: impl AsRef, working_dir: impl AsRef) -> Self { Self { repository_root: repository_root.as_ref().to_path_buf(), working_dir: working_dir.as_ref().to_path_buf(), @@ -255,8 +261,12 @@ impl GitCommand { } /// A top-level command for git. +/// +/// The full reference is available in the [official docs](https://git-scm.com/docs/git#_git_commands). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Command { + /// Clone a repository into a new directory. + Clone, /// Remove untracked files from the working tree. Clean, /// Show changes between commits, commit and working tree, etc. @@ -276,6 +286,7 @@ pub enum Command { impl AsRef for Command { fn as_ref(&self) -> &OsStr { OsStr::new(match self { + Command::Clone => "clone", Command::Clean => "clean", Command::Diff => "diff", Command::Init => "init", diff --git a/build/ci_utils/src/programs/shaderc.rs b/build/ci_utils/src/programs/shaderc.rs new file mode 100644 index 0000000000..47aa807318 --- /dev/null +++ b/build/ci_utils/src/programs/shaderc.rs @@ -0,0 +1,36 @@ +//! A collection of tools, libraries, and tests for Vulkan shader compilation. +//! +//! See the [GitHub repository](https://github.com/google/shaderc) for more information. + +use crate::prelude::*; + + + +// ============= +// === glslc === +// ============= + +/// A command-line GLSL/HLSL to SPIR-V compiler with Clang-compatible arguments. +#[derive(Clone, Copy, Debug, Default)] +pub struct Glslc; + +impl Program for Glslc { + fn executable_name(&self) -> &'static str { + "glslc" + } +} + + +// ================= +// === spirv-opt === +// ================= + +/// SPIR-V Optimizer. +#[derive(Clone, Copy, Debug, Default)] +pub struct SpirvOpt; + +impl Program for SpirvOpt { + fn executable_name(&self) -> &'static str { + "spirv-opt" + } +} diff --git a/build/ci_utils/src/programs/spirv_cross.rs b/build/ci_utils/src/programs/spirv_cross.rs new file mode 100644 index 0000000000..0a6f2bcc8a --- /dev/null +++ b/build/ci_utils/src/programs/spirv_cross.rs @@ -0,0 +1,16 @@ +//! Wrapper for [spirv-cross](https://github.com/KhronosGroup/SPIRV-Cross). + +use crate::prelude::Program; + + + +/// SPIRV-Cross is a practical tool and library for performing reflection on SPIR-V and +/// disassembling SPIR-V back to high level languages. +#[derive(Clone, Copy, Debug, Default)] +pub struct SpirvCross; + +impl Program for SpirvCross { + fn executable_name(&self) -> &'static str { + "spirv-cross" + } +} diff --git a/build/ci_utils/src/programs/strip.rs b/build/ci_utils/src/programs/strip.rs new file mode 100644 index 0000000000..cba9f85e57 --- /dev/null +++ b/build/ci_utils/src/programs/strip.rs @@ -0,0 +1,13 @@ +use crate::prelude::*; + + + +/// Program that discards symbols and other data from object files. +#[derive(Debug, Clone, Copy)] +pub struct Strip; + +impl Program for Strip { + fn executable_name(&self) -> &str { + "strip" + } +} diff --git a/build/ci_utils/src/serde.rs b/build/ci_utils/src/serde.rs index 424ce0b2e0..0933b4a64c 100644 --- a/build/ci_utils/src/serde.rs +++ b/build/ci_utils/src/serde.rs @@ -121,3 +121,33 @@ pub mod via_string { T::from_str(&text).map_err(D::Error::custom) } } + +/// Like [`via_string`] but for optional values. If the string is not present, `None` is recognized. +pub mod via_string_opt { + use super::*; + + /// Serializer, that uses [`Display`] trait. + pub fn serialize(value: &Option, ser: S) -> std::result::Result + where + S: Serializer, + T: Display, { + if let Some(value) = value { + ser.collect_str(value) + } else { + ser.serialize_none() + } + } + + /// Deserializer, that uses [`FromString`] trait. + pub fn deserialize<'de, D, T>(de: D) -> std::result::Result, D::Error> + where + D: Deserializer<'de>, + T: FromString, { + let text = Option::::deserialize(de)?; + if let Some(text) = text { + T::from_str(&text).map(Some).map_err(D::Error::custom) + } else { + Ok(None) + } + } +} diff --git a/build/cli/Cargo.toml b/build/cli/Cargo.toml index 04dec7673a..4313c5d6c6 100644 --- a/build/cli/Cargo.toml +++ b/build/cli/Cargo.toml @@ -18,12 +18,10 @@ futures-util = "0.3.17" glob = "0.3.0" humantime = "2.1.0" ide-ci = { path = "../ci_utils" } -octocrab = { git = "https://github.com/enso-org/octocrab", default-features = false, features = [ - "rustls" -] } +octocrab = { workspace = true } serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.68" -serde_yaml = "0.9.10" +serde_yaml = { workspace = true } strum = { version = "0.24.0", features = ["derive"] } tempfile = "3.2.0" tokio = { workspace = true } diff --git a/build/cli/src/arg.rs b/build/cli/src/arg.rs index 9834338037..8f9079678c 100644 --- a/build/cli/src/arg.rs +++ b/build/cli/src/arg.rs @@ -133,8 +133,6 @@ pub enum Target { Fmt, /// Release-related subcommand. Release(release::Target), - /// Regenerate GitHub Actions workflows. - CiGen, /// Regenerate `syntax2` library (new parser). JavaGen(java_gen::Target), /// Check if the changelog has been updated. Requires CI environment. diff --git a/build/cli/src/bin/enso-remove-draft-releases.rs b/build/cli/src/bin/enso-remove-draft-releases.rs index 7bc5307ee8..79035a0f93 100644 --- a/build/cli/src/bin/enso-remove-draft-releases.rs +++ b/build/cli/src/bin/enso-remove-draft-releases.rs @@ -6,7 +6,7 @@ use enso_build_cli::prelude::*; -use enso_build::setup_octocrab; +use ide_ci::github::setup_octocrab; use ide_ci::github::RepoRef; use ide_ci::io::web::handle_error_response; use ide_ci::log::setup_logging; diff --git a/build/cli/src/lib.rs b/build/cli/src/lib.rs index af13b8b39d..550dbb22bd 100644 --- a/build/cli/src/lib.rs +++ b/build/cli/src/lib.rs @@ -21,7 +21,6 @@ // ============== pub mod arg; -pub mod ci_gen; @@ -65,7 +64,6 @@ use enso_build::project::IsTarget; use enso_build::project::IsWatchable; use enso_build::project::IsWatcher; use enso_build::project::ProcessWrapper; -use enso_build::setup_octocrab; use enso_build::source::BuildTargetJob; use enso_build::source::CiRunSource; use enso_build::source::ExternalSource; @@ -81,6 +79,7 @@ use ide_ci::actions::workflow::is_in_env; use ide_ci::cache::Cache; use ide_ci::define_env_var; use ide_ci::fs::remove_if_exists; +use ide_ci::github::setup_octocrab; use ide_ci::global; use ide_ci::ok_ready_boxed; use ide_ci::programs::cargo; @@ -472,7 +471,7 @@ impl Processor { arg::ide::Command::Build { params } => self.build_ide(params).void_ok().boxed(), arg::ide::Command::Upload { params, release_id } => { let build_job = self.build_ide(params); - let release = ide_ci::github::release::ReleaseHandle::new( + let release = ide_ci::github::release::Handle::new( &self.octocrab, self.remote_repo.clone(), release_id, @@ -811,9 +810,8 @@ pub async fn main_internal(config: Option) -> Result let git_clean = clean::clean_except_for(&ctx.repo_root, exclusions, dry_run); let clean_cache = async { - if cache { - ide_ci::fs::tokio::perhaps_remove_dir_if_exists(dry_run, ctx.cache.path()) - .await?; + if cache && !dry_run { + ide_ci::fs::tokio::remove_dir_if_exists(ctx.cache.path()).await?; } Result::Ok(()) }; @@ -876,9 +874,6 @@ pub async fn main_internal(config: Option) -> Result enso_build::release::promote_release(&ctx, designation).await?; } }, - Target::CiGen => ci_gen::generate( - &enso_build::paths::generated::RepoRootGithubWorkflows::new(cli.repo_path), - )?, Target::JavaGen(command) => { let repo_root = ctx.repo_root.clone(); async move { diff --git a/build/enso-formatter/Cargo.toml b/build/enso-formatter/Cargo.toml index db6c0eb01b..817057d92e 100644 --- a/build/enso-formatter/Cargo.toml +++ b/build/enso-formatter/Cargo.toml @@ -5,6 +5,6 @@ authors = ["Enso Team "] edition = "2021" [dependencies] -regex = "1" +regex = { workspace = true } ide-ci = { path = "../ci_utils" } tokio = { workspace = true } diff --git a/build/macros/Cargo.toml b/build/macros/Cargo.toml index 3ae9636a62..817b70bcb1 100644 --- a/build/macros/Cargo.toml +++ b/build/macros/Cargo.toml @@ -13,7 +13,7 @@ enso-build-base = { path = "../base" } itertools = "0.10.5" proc-macro2 = "1.0" quote = "1.0" -regex = "1.6.0" -serde_yaml = "0.9.14" +regex = { workspace = true } +serde_yaml = { workspace = true } shrinkwraprs = "0.3.0" syn = "1.0" diff --git a/build/shader-tools/Cargo.toml b/build/shader-tools/Cargo.toml new file mode 100644 index 0000000000..93a2634cac --- /dev/null +++ b/build/shader-tools/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "enso-build-shader-tools" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +html_parser = "0.6.3" +ide-ci = { path = "../ci_utils" } +octocrab = { workspace = true } +regex = "1.7.1" +tempfile = "3.3.0" +tokio = { workspace = true } diff --git a/build/shader-tools/src/bin/create.rs b/build/shader-tools/src/bin/create.rs new file mode 100644 index 0000000000..7ed52f3ced --- /dev/null +++ b/build/shader-tools/src/bin/create.rs @@ -0,0 +1,30 @@ +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] + +use enso_build_shader_tools::prelude::*; + +use ide_ci::prelude::setup_logging; + + + +#[tokio::main] +async fn main() -> Result { + setup_logging()?; + let handle = enso_build_shader_tools::repo_handle_from_env().await?; + let latest_version = handle + .latest_release() + .await + .and_then(|r| Version::from_str(&r.tag_name)) + .unwrap_or_else(|_| Version::new(0, 0, 0)); + let next_version = latest_version.next_minor(); + let release = enso_build_shader_tools::create_release(handle, next_version.to_string()).await?; + ide_ci::actions::workflow::set_output( + enso_build_shader_tools::ENSO_RELEASE_ID.as_str(), + &release.id, + ) + .await?; + Ok(()) +} diff --git a/build/shader-tools/src/bin/package.rs b/build/shader-tools/src/bin/package.rs new file mode 100644 index 0000000000..ddc6a4e2f2 --- /dev/null +++ b/build/shader-tools/src/bin/package.rs @@ -0,0 +1,23 @@ +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] + +use enso_build_shader_tools::prelude::*; + +use ide_ci::prelude::setup_logging; + + + +#[tokio::main] +async fn main() -> Result { + setup_logging()?; + let release = enso_build_shader_tools::release_handle_from_env().await?; + let asset_name = format!("shader-tools-{TARGET_OS}-{TARGET_ARCH}.tar.gz"); + let temp_dir = tempfile::tempdir()?; + let output_archive = temp_dir.path().join(&asset_name); + enso_build_shader_tools::create_package(&output_archive).await?; + release.upload_asset_file(&output_archive).await?; + Ok(()) +} diff --git a/build/shader-tools/src/bin/publish.rs b/build/shader-tools/src/bin/publish.rs new file mode 100644 index 0000000000..cd77c55cad --- /dev/null +++ b/build/shader-tools/src/bin/publish.rs @@ -0,0 +1,19 @@ +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] + +use enso_build_shader_tools::prelude::*; + +use ide_ci::prelude::setup_logging; + + + +#[tokio::main] +async fn main() -> Result { + setup_logging()?; + let release = enso_build_shader_tools::release_handle_from_env().await?; + release.publish().await?; + Ok(()) +} diff --git a/build/shader-tools/src/ci.rs b/build/shader-tools/src/ci.rs new file mode 100644 index 0000000000..b96dc39db1 --- /dev/null +++ b/build/shader-tools/src/ci.rs @@ -0,0 +1,90 @@ +//! Logic that generates CI workflow definition that is used to build the shaderc packages. + +use crate::prelude::*; + +use crate::ENSO_RELEASE_ID; + +use ide_ci::actions::workflow::definition::checkout_repo_step; +use ide_ci::actions::workflow::definition::Job; +use ide_ci::actions::workflow::definition::RunnerLabel; +use ide_ci::actions::workflow::definition::Step; +use ide_ci::actions::workflow::definition::Workflow; +use ide_ci::actions::workflow::definition::WorkflowToWrite; +use ide_ci::github::GITHUB_TOKEN; + + + +/// Binaries provided by this crate that CI wants to invoke. +#[derive(Clone, Copy, Debug)] +pub enum Binary { + /// Create a new draft release and exposes its ID to next steps. + Create, + /// Upload a package with shader tools to the release. Needs to be run on each platform. + Package, + /// Publish the release. + Publish, +} + +impl Display for Binary { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let name = match self { + Self::Create => "create", + Self::Package => "package", + Self::Publish => "publish", + }; + write!(f, "{name}") + } +} + +/// Generate a step definition that runs given binary from this crate. +pub fn run_bin(binary: Binary) -> Step { + let pkg_name = env!("CARGO_PKG_NAME"); + let command = format!("cargo run --package {pkg_name} --bin {binary}"); + ide_ci::actions::workflow::definition::shell(command) +} + +/// Job that invokes the given binary from this crate. +/// +/// The job will have access to the `CI_PRIVATE_TOKEN` secret, so it can manage releases on other +/// repositories. +pub fn job_that_runs(binary: Binary, runs_on: RunnerLabel, expose_output: Option<&str>) -> Job { + let checkout_steps = checkout_repo_step(); + + let mut job = Job::new(format!("Run {binary} ({runs_on:?})"), [runs_on]); + job.steps.extend(checkout_steps); + let main_step = run_bin(binary).with_secret_exposed_as("CI_PRIVATE_TOKEN", GITHUB_TOKEN); + job.add_step_with_output(main_step, expose_output); + job +} + +/// Generate a workflow that builds shaderc packages for all platforms and releases them. +pub fn generate_workflow(path: impl Into) -> WorkflowToWrite { + // Once CMake is added, we might want to switch to self-hosted runners. + // On the other hand, there is little incentive to do so, as the build is fast enough and not + // part of our "usual" CI pipeline.. + let linux = RunnerLabel::LinuxLatest; + let windows = RunnerLabel::WindowsLatest; + let macos = RunnerLabel::MacOSLatest; + + let mut workflow = Workflow::new("Package Tools"); + workflow.on.workflow_dispatch(default()); + let create_release_job = job_that_runs(Binary::Create, linux, Some(ENSO_RELEASE_ID.as_ref())); + let create_job_id = workflow.add_job(create_release_job); + + let package_job_ids = [linux, windows, macos] + .into_iter() + .map(|target| { + let mut job = job_that_runs(Binary::Package, target, None); + workflow.expose_outputs(&create_job_id, &mut job); + workflow.add_job(job) + }) + .collect_vec(); + + let mut publish_job = job_that_runs(Binary::Publish, linux, None); + workflow.expose_outputs(&create_job_id, &mut publish_job); + for package_job_id in package_job_ids { + publish_job.needs(package_job_id); + } + workflow.add_job(publish_job); + WorkflowToWrite { workflow, path: path.into(), source: file!().into() } +} diff --git a/build/shader-tools/src/lib.rs b/build/shader-tools/src/lib.rs new file mode 100644 index 0000000000..e410eac729 --- /dev/null +++ b/build/shader-tools/src/lib.rs @@ -0,0 +1,95 @@ +//! This crate deals with packaging and shader-tools. +//! +//! Shader tools package contains all the tools that we use for shader precompilation. We release it +//! through https://github.com/enso-org/shader-tools/, so build script can download binaries from +//! there, rather than requiring the user to install them manually. + +#![recursion_limit = "1024"] +// === Features === +#![feature(default_free_fn)] +#![feature(option_result_contains)] +#![feature(trait_alias)] +#![feature(hash_drain_filter)] +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] +// === Non-Standard Linter Configuration === +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] + +use prelude::*; + +use ide_ci::define_env_var; +use ide_ci::github::release; +use ide_ci::github::repo; +use ide_ci::github::setup_octocrab; +use ide_ci::github::RepoRef; +use ide_ci::goodies::shader_tools::SHADER_TOOLS_REPO; + + +// ============== +// === Export === +// ============== + +pub mod ci; +pub mod shaderc; +pub mod spirv_cross; + +pub use ide_ci::prelude; + + + +define_env_var! { + /// ID of the release that we are deploying built assets to. + ENSO_RELEASE_ID, octocrab::models::ReleaseId; +} + +/// Create a package with shader tools for the current platform. +/// +/// The package can be later uploaded as a release asset. +#[context("Failed to create a package with shader tools.")] +pub async fn create_package(output_archive: &Path) -> Result { + let package = tempfile::tempdir()?; + shaderc::generate_stripped_package(package.path()).await?; + spirv_cross::generate_spirv_cross_package(package.path()).await?; + ide_ci::archive::compress_directory_contents(&output_archive, package.path()).await?; + Ok(()) +} + +/// Get the handle to shader tools repository using system environment. +/// +/// Note that currently the repository is hardcoded to `enso-org/shader-tools`. +#[context("Failed to get the handle to shader tools repository.")] +pub async fn repo_handle_from_env() -> Result>> { + let octo = setup_octocrab().await?; + let handle = SHADER_TOOLS_REPO.into_handle(&octo); + Ok(handle) +} + +/// Get the handle to the release that we are deploying assets to. +/// +/// Requires some environment setup, done by [`create_release`]. +#[context("Failed to get the handle to the release that we are deploying assets to.")] +pub async fn release_handle_from_env() -> Result { + let repo::Handle { octocrab, repo } = repo_handle_from_env().await?; + let handle = release::Handle::new(&octocrab, repo, ENSO_RELEASE_ID.get()?); + Ok(handle) +} + +/// Create a draft release for the current version of the shader tools. +#[context("Failed to create a new draft release of the shader tools.")] +pub async fn create_release( + handle: repo::Handle, + tag: impl AsRef, +) -> Result { + let release = handle.repos().releases().create(&tag).draft(true).send().await?; + ide_ci::actions::workflow::set_output(ENSO_RELEASE_ID.name, &release.id).await?; + Ok(release) +} diff --git a/build/shader-tools/src/shaderc.rs b/build/shader-tools/src/shaderc.rs new file mode 100644 index 0000000000..95ddb1420e --- /dev/null +++ b/build/shader-tools/src/shaderc.rs @@ -0,0 +1,109 @@ +//! Downloading and repackaging the `shaderc` collection of tools. + +use crate::prelude::*; + +use ide_ci::programs::shaderc::Glslc; +use ide_ci::programs::shaderc::SpirvOpt; +use ide_ci::programs::Strip; + + + +/// The binaries from the `shaderc` collection that we actually use. +pub fn binaries_to_package() -> [&'static str; 2] { + [Glslc.executable_name(), SpirvOpt.executable_name()] +} + +/// Regex that matches URLs. +/// ``` +/// # use enso_build_shader_tools::shaderc::url_regex; +/// let url_text = +/// "https://storage.googleapis.com/shaderc/badges/build_link_windows_vs2017_release.html"; +/// assert!(url_regex().is_match(url_text)); +/// ``` +pub fn url_regex() -> regex::Regex { + // As per https://uibakery.io/regex-library/url + regex::Regex::new( + r#"https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)"#, + ).unwrap() +} + +/// Download URL for shaderc package as [advertised on their page](https://github.com/google/shaderc/blob/main/downloads.md). +/// Note that this URL actually contains a redirection to the recent CI build. The redirection can +/// be resolved using [`get_redirection_target`]. It is ``-based redirection, not the 3xx one. +pub fn download_url() -> Result { + match TARGET_OS { + OS::Linux => + "https://storage.googleapis.com/shaderc/badges/build_link_linux_gcc_release.html", + OS::MacOS => + "https://storage.googleapis.com/shaderc/badges/build_link_macos_clang_release.html", + OS::Windows => + "https://storage.googleapis.com/shaderc/badges/build_link_windows_vs2017_release.html", + _ => bail!("Unsupported OS: {}.", TARGET_OS), + } + .parse2() +} + +/// Get the URL that is the target of the redirection used by `shaderc` download page. +/// +/// ``` +/// # use enso_build_shader_tools::shaderc::get_redirection_target; +/// let html = r#""#; +/// let url = get_redirection_target(html).unwrap(); +/// assert_eq!(url.as_str(),"https://storage.googleapis.com/shaderc/artifacts/prod/graphics_shader_compiler/shaderc/windows/continuous_release_2017/406/20230118-144628/install.zip"); +/// ``` +#[context("Failed to get the redirection target from the HTML text: {html}")] +pub fn get_redirection_target(html: &str) -> Result { + let regex = url_regex(); + let dom = html_parser::Dom::parse(html)?; + debug!("{:#?}", &dom); + let [html_parser::Node::Element(element)] = dom.children.as_slice() else { + bail!("Expected one child node."); + }; + ensure!(element.name == "meta", "Expected meta tag."); + let Some(Some(content)) = element.attributes.get("content") else { + bail!("Expected content attribute."); + }; + let Some(url_match) = regex.captures(content).and_then(|captures| captures.get(0)) else { + bail!("Expected URL."); + }; + url_match.as_str().parse2() +} + +/// Download the `shaderc` package. +#[context("Failed to download the shaderc package.")] +pub async fn download_package(output_dir: &Path) -> Result { + let url = download_url()?; + let body = ide_ci::io::download_all(url).await?; + let text = std::str::from_utf8(&body)?; + let url = get_redirection_target(text)?; + + let temp = tempfile::tempdir()?; + let downloaded_archive = ide_ci::io::download_to_dir(url, &temp).await?; + info!("Download to {} complete.", downloaded_archive.display()); + ide_ci::archive::extract_to(&downloaded_archive, &output_dir).await +} + +/// Strip down the `shaderc` package to the bare minimum that we require. +#[context("Failed to strip down the shaderc package.")] +pub async fn strip_package(package_path: &Path, output_package: &Path) -> Result { + let extracted_content_dir = package_path.join("install"); + for binary in binaries_to_package() { + let file = Path::new("bin").join(binary).with_executable_extension(); + let path = + ide_ci::fs::tokio::copy_between(&extracted_content_dir, &output_package, &file).await?; + // Only linux packages contain the debug symbols, which make them really heavy. + if TARGET_OS == OS::Linux { + Strip.cmd()?.arg(&path).run_ok().await?; + } + } + Ok(()) +} + +/// Download and strip down the `shaderc` package. +#[context("Failed to generate stripped down shaderc package.")] +pub async fn generate_stripped_package(output_dir: &Path) -> Result { + let extracted_archive = tempfile::tempdir()?; + let extracted_archive = extracted_archive.path(); + download_package(extracted_archive).await?; + strip_package(extracted_archive, output_dir).await +} diff --git a/build/shader-tools/src/spirv_cross.rs b/build/shader-tools/src/spirv_cross.rs new file mode 100644 index 0000000000..06b0a58a20 --- /dev/null +++ b/build/shader-tools/src/spirv_cross.rs @@ -0,0 +1,72 @@ +//! Building and packaging [`SPIRV-Cross`](SpirvCross). + +use crate::prelude::*; + +use ide_ci::programs::cmake; +use ide_ci::programs::cmake::SetVariable; +use ide_ci::programs::spirv_cross::SpirvCross; +use ide_ci::programs::vs::apply_dev_environment; +use ide_ci::programs::vs::Cl; +use ide_ci::programs::Git; + + + +/// Address of the SPIRV-Cross GitHub repository. +pub const REPOSITORY_URL: &str = "https://github.com/KhronosGroup/SPIRV-Cross"; + +/// Binaries that we want to package. +pub fn binaries_to_package() -> [&'static str; 1] { + [SpirvCross.executable_name()] +} + +/// Build and install the SPIRV-Cross to the given directory. +pub async fn build_and_install( + source_dir: impl AsRef, + install_dir: impl AsRef, +) -> Result { + let build_dir = tempfile::tempdir()?; + cmake::generate(&source_dir, &build_dir)? + // We only want the tool binary, we don't want to bother with building tests. + .apply(&SetVariable::option("SPIRV_CROSS_ENABLE_TESTS", false)) + // Note [Configuration] + .apply(&cmake::build_type(cmake::Configuration::Release)) + .run_ok() + .await?; + + cmake::build(&build_dir)? + // Note [Configuration] + .apply(&cmake::BuildOption::Configuration(cmake::Configuration::Release)) + .run_ok() + .await?; + + // Note [Configuration] + // ~~~~~~~~~~~~~~~~~~~~ + // We pass the Release configuration twice, but it will be used only once. It will be used in + // the generation phase, if the generator is a single-config one. Otherwise, it will be used in + // the build phase. We do not want to assume any particular kind of generator, so we support + // both cases. + + ide_ci::fs::tokio::reset_dir(&install_dir).await?; + cmake::install(&build_dir, &install_dir)?.run_ok().await?; + Ok(()) +} + +/// Download sources of the SPIRV-Cross, build them and package the binary we need. +pub async fn generate_spirv_cross_package(output_dir: &Path) -> Result { + if TARGET_OS == OS::Windows && Cl.lookup().is_err() { + apply_dev_environment().await?; + } + + let path = tempfile::tempdir()?; + let path = path.as_ref(); + let install_dir = path.join("_install"); + ide_ci::fs::tokio::reset_dir(&path).await?; + let _git = Git.clone(&path, &(REPOSITORY_URL.try_into()?)).await?; + + build_and_install(&path, &install_dir).await?; + for binary in binaries_to_package() { + let relative_binary_path = PathBuf::from_iter(["bin", binary]).with_executable_extension(); + ide_ci::fs::tokio::copy_between(&install_dir, &output_dir, relative_binary_path).await?; + } + Ok(()) +} diff --git a/lib/rust/config-reader/Cargo.toml b/lib/rust/config-reader/Cargo.toml index 335da7ce80..383f59829c 100644 --- a/lib/rust/config-reader/Cargo.toml +++ b/lib/rust/config-reader/Cargo.toml @@ -9,5 +9,5 @@ crate-type = ["rlib"] [dependencies] serde = { version = "1.0" } -serde_yaml = { version = "0.8.23" } +serde_yaml = { workspace = true } Inflector = { version = "0.11.4" } diff --git a/lib/rust/ensogl/app/theme/hardcoded/Cargo.toml b/lib/rust/ensogl/app/theme/hardcoded/Cargo.toml index 36e298af3d..8efeb8849b 100644 --- a/lib/rust/ensogl/app/theme/hardcoded/Cargo.toml +++ b/lib/rust/ensogl/app/theme/hardcoded/Cargo.toml @@ -9,3 +9,5 @@ crate-type = ["rlib", "cdylib"] [dependencies] ensogl-core = { path = "../../../core" } +enso-shapely = { path = "../../../../shapely" } +enso-prelude = { path = "../../../../prelude" } diff --git a/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs b/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs index 6d647961f6..bae80583c7 100644 --- a/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs +++ b/lib/rust/ensogl/app/theme/hardcoded/src/lib.rs @@ -15,6 +15,9 @@ #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] +use enso_prelude::*; + +use enso_shapely::before_main; use ensogl_core::prelude::ImString; @@ -730,3 +733,19 @@ define_themes! { [light:0, dark:1] size = 12.0, 12.0; } } + + +// ========================== +// === Theme registration === +// ========================== + +/// Default theme registration. The theme is registered and enabled in a before-main entry point in +/// order for it to be visible when shaders are being gathered during compilation phase. This makes +/// themes not switchable at runtime, which we might want to fix somehow in the future. +#[before_main(2)] +pub fn enable_default_theme() { + let themes = ensogl_core::display::world::with_context(|t| t.theme_manager.clone()); + builtin::light::register(&themes); + builtin::light::enable(&themes); + themes.update(); +} diff --git a/lib/rust/ensogl/component/list-view/src/lib.rs b/lib/rust/ensogl/component/list-view/src/lib.rs index 18fc8f5c06..14e9ebdf67 100644 --- a/lib/rust/ensogl/component/list-view/src/lib.rs +++ b/lib/rust/ensogl/component/list-view/src/lib.rs @@ -752,7 +752,6 @@ mod tests { use approx::assert_relative_eq; use enso_frp::future::EventOutputExt; - use ensogl_core::display::style::data::DataMatch; #[test] fn navigating_list_view_with_keyboard() { @@ -805,15 +804,12 @@ mod tests { fn selection_position() { use ensogl_hardcoded_theme::widget::list_view as theme; let app = Application::new("root"); - ensogl_hardcoded_theme::builtin::light::register(&app); - ensogl_hardcoded_theme::builtin::light::enable(&app); let style_sheet = &app.display.default_scene.style_sheet; style_sheet.set(theme::highlight::height, entry::HEIGHT); - let padding = style_sheet.value(theme::padding).unwrap().number().unwrap(); let list_view = ListView::::new(&app); let provider = AnyModelProvider::::new(vec!["Entry 1", "Entry 2", "Entry 3", "Entry 4"]); - list_view.resize(Vector2(100.0, entry::HEIGHT * 3.0 + padding * 2.0)); + list_view.resize(Vector2(100.0, entry::HEIGHT * 3.0)); list_view.set_entries(provider); list_view.select_entry(Some(0)); assert_relative_eq!(list_view.selection_position_target.value().x, 0.0); diff --git a/lib/rust/ensogl/component/text/src/font/embedded/build.rs b/lib/rust/ensogl/component/text/src/font/embedded/build.rs index 10311813de..2d58351099 100644 --- a/lib/rust/ensogl/component/text/src/font/embedded/build.rs +++ b/lib/rust/ensogl/component/text/src/font/embedded/build.rs @@ -138,7 +138,7 @@ mod google_fonts { use super::*; use crate::CodeGenerator; - use enso_build::ide::web::download_google_font; + use enso_build::ide::web::google_font::download_google_font; #[derive(Debug)] pub struct FaceDefinition { @@ -149,6 +149,7 @@ mod google_fonts { /// A description of downloaded file. #[derive(Debug, Clone)] pub struct DownloadedFile { + /// Path relative to the output directory. name: String, } @@ -156,9 +157,13 @@ mod google_fonts { name: impl AsRef, out_dir: &Path, ) -> Result> { - let octocrab = enso_build::setup_octocrab().await?; - let result = download_google_font(&octocrab, name.as_ref(), out_dir).await?; - Ok(result.into_iter().map(|content| DownloadedFile { name: content.name }).collect()) + let octocrab = ide_ci::github::setup_octocrab().await?; + let cache = ide_ci::cache::Cache::new_default().await?; + let result = download_google_font(&cache, &octocrab, name.as_ref(), out_dir).await?; + result + .into_iter() + .map(|font| Ok(DownloadedFile { name: font.try_file_name()?.as_str().into() })) + .try_collect() } pub async fn load(out_dir: &Path, buffer: &mut CodeGenerator, family_name: &str) -> Result { diff --git a/lib/rust/ensogl/component/text/src/font/glyph.rs b/lib/rust/ensogl/component/text/src/font/glyph.rs index 5172958836..b1e6da1b19 100644 --- a/lib/rust/ensogl/component/text/src/font/glyph.rs +++ b/lib/rust/ensogl/component/text/src/font/glyph.rs @@ -160,7 +160,9 @@ impl ShapeData { mod glyph_shape { use super::*; - ensogl_core::shape! { + // FIXME[WD]: We are using old shape generator here which does not use shader precompilation. + // To be fixed in the next PR: https://www.pivotaltracker.com/story/show/184304289 + ensogl_core::shape_old! { type SystemData = SystemData; type ShapeData = ShapeData; flavor = ShapeData::flavor; @@ -179,10 +181,9 @@ mod glyph_shape { } } -impl ensogl_core::display::shape::CustomSystemData for SystemData { +impl display::shape::CustomSystemData for SystemData { fn new( - scene: &Scene, - data: &ensogl_core::display::shape::ShapeSystemStandardData, + data: &display::shape::ShapeSystemStandardData, shape_data: &ShapeData, ) -> Self { let font = &shape_data.font; @@ -195,8 +196,10 @@ impl ensogl_core::display::shape::CustomSystemData for Syste data.model.do_not_use_shape_definition.set(true); sprite_system.unsafe_set_alignment(alignment::Dim2::left_bottom()); - scene.variables.add("msdf_range", GlyphRenderInfo::MSDF_PARAMS.range as f32); - scene.variables.add("msdf_size", size); + display::world::with_context(|t| { + t.variables.add("msdf_range", GlyphRenderInfo::MSDF_PARAMS.range as f32); + t.variables.add("msdf_size", size); + }); symbol.variables().add_uniform_or_panic("atlas", &font.atlas); diff --git a/lib/rust/ensogl/core/src/application.rs b/lib/rust/ensogl/core/src/application.rs index d65b24a756..cfeb53f46e 100644 --- a/lib/rust/ensogl/core/src/application.rs +++ b/lib/rust/ensogl/core/src/application.rs @@ -4,10 +4,8 @@ use crate::prelude::*; use enso_web::traits::*; use crate::application::command::FrpNetworkProvider; -use crate::control::callback; use crate::display; use crate::display::scene::DomPath; -use crate::display::style::theme; use crate::display::world::World; use crate::gui::cursor::Cursor; use crate::system::web; @@ -68,15 +66,13 @@ pub struct Application { #[derive(Debug)] #[allow(missing_docs)] pub struct ApplicationData { - pub logger: Logger, - pub cursor: Cursor, - pub display: World, - pub commands: command::Registry, - pub shortcuts: shortcut::Registry, - pub views: view::Registry, - pub themes: theme::Manager, - pub frp: Frp, - update_themes_handle: callback::Handle, + pub logger: Logger, + pub cursor: Cursor, + pub display: World, + pub commands: command::Registry, + pub shortcuts: shortcut::Registry, + pub views: view::Registry, + pub frp: Frp, } impl ApplicationData { @@ -99,23 +95,11 @@ impl Application { let shortcuts = shortcut::Registry::new(&logger, &scene.mouse.frp, &scene.keyboard.frp, &commands); let views = view::Registry::create(&logger, &display, &commands, &shortcuts); - let themes = theme::Manager::from(&display.default_scene.style_sheet); let cursor = Cursor::new(&display.default_scene); display.add_child(&cursor); - let update_themes_handle = display.on.before_frame.add(f_!(themes.update())); let frp = Frp::new(); - let data = ApplicationData { - logger, - cursor, - display, - commands, - shortcuts, - views, - themes, - update_themes_handle, - frp, - }; + let data = ApplicationData { logger, cursor, display, commands, shortcuts, views, frp }; Self { inner: Rc::new(data) }.init() } @@ -147,12 +131,6 @@ impl display::Object for Application { } } -impl AsRef for Application { - fn as_ref(&self) -> &theme::Manager { - &self.themes - } -} - // ================== diff --git a/lib/rust/ensogl/core/src/application/args.rs b/lib/rust/ensogl/core/src/application/args.rs index 4278ea7bdd..b774305c5c 100644 --- a/lib/rust/ensogl/core/src/application/args.rs +++ b/lib/rust/ensogl/core/src/application/args.rs @@ -5,6 +5,63 @@ use crate::prelude::*; +// ============================= +// === Ident Case Conversion === +// ============================= + +/// Convert snake to camel case name. +pub fn snake_case_to_camel_case(name: &str) -> String { + let mut output = String::new(); + let mut capitalize_next = false; + for c in name.chars() { + if c == '_' { + capitalize_next = true; + } else if capitalize_next { + output.push(c.to_ascii_uppercase()); + capitalize_next = false; + } else { + output.push(c); + } + } + output +} + + + +// ==================== +// === OptArgReader === +// ==================== + +auto trait NotOption {} +impl !NotOption for Option {} + +/// Argument parser that checks if the desired parameter is of an [`Option`] type. If it is not, +/// the call is redirected to [`ArgReader`]. +#[allow(missing_docs)] +pub trait OptArgReader: Sized { + fn read_arg(input: &Option) -> Result; +} + +impl OptArgReader for Option { + fn read_arg(input: &Option) -> Result { + match input.as_ref() { + None => Ok(None), + Some(str) => ArgReader::read_arg(str.as_ref()).map(Some), + } + } +} + +impl OptArgReader for T { + fn read_arg(input: &Option) -> Result { + match input.as_ref() { + None => Err(anyhow!("Missing required argument.")), + Some(str) => ArgReader::read_arg(str.as_ref()), + } + } +} + + + // ================= // === ArgReader === // ================= @@ -16,37 +73,36 @@ pub trait ArgMarker {} /// Trait used to convert provided string arguments to the desired type. #[allow(missing_docs)] pub trait ArgReader: Sized { - fn read_arg(str: String) -> Option; + fn read_arg(str: &str) -> Result; } - // === Default === /// Helper trait used to disambiguate overlapping impls of [`ArgReader`]. #[allow(missing_docs)] pub trait ArgReaderFromString: Sized { - fn read_arg_from_string(str: String) -> Option; + fn read_arg_from_string(str: &str) -> Result; } impl ArgReaderFromString for T -where String: TryInto +where for<'t> &'t str: TryInto { - fn read_arg_from_string(str: String) -> Option { - str.try_into().ok() + fn read_arg_from_string(input: &str) -> Result { + input.try_into().map_err(|_| anyhow!("Cannot convert '{input}' to argument value.")) } } impl ArgReaderFromString for T { - default fn read_arg_from_string(_: String) -> Option { - unreachable!() + default fn read_arg_from_string(input: &str) -> Result { + Err(anyhow!("Cannot convert '{input}' to argument value.")) } } -impl ArgMarker for T where T: TryFrom {} +impl ArgMarker for T where T: for<'t> TryFrom<&'t str> {} impl ArgReader for T where T: ArgMarker { - default fn read_arg(str: String) -> Option { + default fn read_arg(str: &str) -> Result { ArgReaderFromString::read_arg_from_string(str) } } @@ -56,25 +112,31 @@ where T: ArgMarker impl ArgMarker for bool {} impl ArgReader for bool { - fn read_arg(str: String) -> Option { - match &str[..] { - "true" => Some(true), - "false" => Some(false), - "ok" => Some(true), - "fail" => Some(false), - "enabled" => Some(true), - "disabled" => Some(false), - "yes" => Some(true), - "no" => Some(false), - _ => None, + fn read_arg(input: &str) -> Result { + let true_values = ["true", "enabled", "yes", "1"]; + let false_values = ["false", "disabled", "no", "0"]; + if true_values.contains(&input) { + Ok(true) + } else if false_values.contains(&input) { + Ok(false) + } else { + let allowed_values = true_values.iter().chain(false_values.iter()).join(", "); + Err(anyhow!("Cannot parse '{input}' as bool. Allowed values: {allowed_values}.")) } } } +impl ArgMarker for f32 {} +impl ArgReader for f32 { + fn read_arg(input: &str) -> Result { + input.parse().map_err(|_| anyhow!("Cannot parse '{input}' as f32.")) + } +} + impl ArgMarker for semver::Version {} impl ArgReader for semver::Version { - fn read_arg(str: String) -> Option { - semver::Version::parse(&str).ok() + fn read_arg(input: &str) -> Result { + semver::Version::parse(input).map_err(|_| anyhow!("Cannot parse '{input}' as semver.")) } } @@ -91,11 +153,9 @@ impl ArgReader for semver::Version { /// For example, given the following definition: /// ```text /// read_args! { -/// js::global.config { -/// entry : String, -/// project : String, -/// dark_theme : bool, -/// } +/// entry: String, +/// project: String, +/// dark_theme: bool, /// } /// ``` /// @@ -104,8 +164,8 @@ impl ArgReader for semver::Version { /// ```text /// #[derive(Clone, Debug, Default)] /// pub struct Args { -/// pub entry: Option, -/// pub project: Option, +/// pub entry: Option, +/// pub project: Option, /// pub dark_theme: Option, /// } /// @@ -122,7 +182,7 @@ impl ArgReader for semver::Version { /// the conversion will fail, a warning will be raised. #[macro_export] macro_rules! read_args { - ([$($($path:tt)*).*] { $($(#[$($attr:tt)+])* $field:ident : $field_type:ty),* $(,)? }) => { + ($($(#[$($attr:tt)+])* $field:ident : $field_type:ty),* $(,)?) => { mod _READ_ARGS { use super::*; use $crate::prelude::*; @@ -134,8 +194,8 @@ macro_rules! read_args { impl ArgNames { $( /// Name of the field. - pub fn $field(&self) -> &'static str { - stringify!{$field} + pub fn $field(&self) -> String { + $crate::application::args::snake_case_to_camel_case(stringify!{$field}) } )* } @@ -146,53 +206,51 @@ macro_rules! read_args { pub struct Args { $( $(#[$($attr)*])* - pub $field : Option<$field_type> + pub $field : $field_type ),* } impl Args { /// Constructor. fn new() -> Self { - let logger = Logger::new(stringify!{Args}); - let path = vec![$($($path)*),*]; - match ensogl::system::web::Reflect::get_nested_object - (&ensogl::system::web::window,&path).ok() { - None => { - let path = path.join("."); - error!("The config path '{path}' is invalid."); + let js_app = ensogl::system::js::app::app(); + match js_app { + Err(_) => { + error!("Cannot get the JS application. Using default arguments."); default() } - Some(cfg) => { - let keys = ensogl::system::web::Object::keys_vec(&cfg); - let mut keys = keys.into_iter().collect::>(); + Ok(js_app) => { + let mut params = js_app.config().params().to_hash_map(); $( - let name = stringify!{$field}; - let tp = stringify!{$field_type}; - let $field = ensogl::system::web::Reflect:: - get_nested_object_printed_as_string(&cfg,&[name]).ok(); - let $field = $field.map - ($crate::application::args::ArgReader::read_arg); - if $field == Some(None) { - warning!(&logger,"Failed to convert the argument '{name}' \ - value to the '{tp}' type."); - } - let $field = $field.flatten(); - keys.remove(name); + let js_name = $crate::application::args::snake_case_to_camel_case + (stringify!{$field}); + let $field = if let Some(param) = params.remove(&js_name) { + let str_value = param.value(); + let value = $crate::application::args::OptArgReader::read_arg(&str_value); + match value { + Err(err) => { + let tp = stringify!{$field_type}; + error!("Config error. Invalid value '{str_value:?}' for parameter \ + '{js_name}' of type '${tp}'. {err}"); + default() + } + Ok(value) => value, + } + } else { + warn!("Config error. Rust config parameter '{js_name}' not found in \ + JavaScript."); + default() + }; )* - for key in keys { - warning!(&logger,"Unknown config option provided '{key}'."); + for js_name in params.keys() { + warn!("Config error. JavaScript config parameter '{js_name}' not found in \ + Rust."); } Self {$($field),*} } } } - /// This is a dummy function which initializes the arg reading process. This - /// function does nothing, however, in order to call it, the user would need to - /// access a field in the lazy static variable `ARGS`, which would trigger argument - /// parsing process. - pub fn init(&self) {} - /// Reflection mechanism to get string representation of argument names. pub fn names(&self) -> ArgNames { ArgNames } } diff --git a/lib/rust/ensogl/core/src/application/command.rs b/lib/rust/ensogl/core/src/application/command.rs index 445143c8f6..97ce31129c 100644 --- a/lib/rust/ensogl/core/src/application/command.rs +++ b/lib/rust/ensogl/core/src/application/command.rs @@ -200,12 +200,8 @@ impl Registry { let was_registered = self.name_map.borrow().get(label).is_some(); if !was_registered { self.register::(); - warning!( - &self.logger, - "The command provider '{label}' was created but never registered. You should \ - always register available command providers as soon as possible to spread the \ - information about their API." - ); + // FIXME[WD]: The registration should be performed automatically by using before-main + // entry points. }; let id = instance.id(); self.name_map.borrow_mut().get_mut(label).unwrap().push(instance.clone_ref()); diff --git a/lib/rust/ensogl/core/src/application/view.rs b/lib/rust/ensogl/core/src/application/view.rs index 2ad3a981fe..afd47e17f0 100644 --- a/lib/rust/ensogl/core/src/application/view.rs +++ b/lib/rust/ensogl/core/src/application/view.rs @@ -69,12 +69,8 @@ impl Registry { let label = V::label(); let was_registered = self.definitions.borrow().get(label).is_some(); if !was_registered { - warning!( - &self.logger, - "The view '{label}' was created but never registered, performing automatic \ - registration. You should always register available views as soon as possible to \ - enable their default shortcuts and spread the information about their API." - ); + // FIXME[WD]: The registration should be performed automatically by using before-main + // entry points. self.register::(); } let view = V::new(app); diff --git a/lib/rust/ensogl/core/src/display/render/passes/symbols.rs b/lib/rust/ensogl/core/src/display/render/passes/symbols.rs index 399de1ab3b..03f0176e5c 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/symbols.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/symbols.rs @@ -7,8 +7,8 @@ use crate::display::render::pass; use crate::display::scene; use crate::display::scene::layer; use crate::display::scene::UpdateStatus; -use crate::display::symbol::registry::SymbolRegistry; use crate::display::symbol::MaskComposer; +use crate::display::world; @@ -32,27 +32,21 @@ impl Framebuffers { /// Pass for rendering all symbols. The results are stored in the 'color' and 'id' outputs. #[derive(Clone, Debug)] pub struct SymbolsRenderPass { - logger: Logger, - symbol_registry: SymbolRegistry, - layers: scene::HardcodedLayers, - framebuffers: Option, - mask_composer: MaskComposer, + logger: Logger, + layers: scene::HardcodedLayers, + framebuffers: Option, + mask_composer: MaskComposer, } impl SymbolsRenderPass { /// Constructor. - pub fn new( - logger: impl AnyLogger, - symbol_registry: &SymbolRegistry, - layers: &scene::HardcodedLayers, - ) -> Self { + pub fn new(logger: impl AnyLogger, layers: &scene::HardcodedLayers) -> Self { let logger = Logger::new_sub(logger, "SymbolsRenderPass"); - let symbol_registry = symbol_registry.clone_ref(); let layers = layers.clone_ref(); let framebuffers = default(); let mask_composer = MaskComposer::new("pass_mask_color", "pass_layer_color", "pass_layer_id"); - Self { logger, symbol_registry, layers, framebuffers, mask_composer } + Self { logger, layers, framebuffers, mask_composer } } } @@ -168,8 +162,10 @@ impl SymbolsRenderPass { instance.context.clear_bufferfv_with_f32_array(*Context::COLOR, 1, &black_transparent); } - self.symbol_registry.set_camera(&layer.camera()); - self.symbol_registry.render_symbols(&layer.symbols()); + world::with_context(|t| { + t.set_camera(&layer.camera()); + t.render_symbols(&layer.symbols()); + }); layer.for_each_sublayer(|sublayer| { if sublayer.flags.contains(layer::LayerFlags::MAIN_PASS_VISIBLE) { self.render_layer(instance, &sublayer, scissor_stack, was_ever_masked); diff --git a/lib/rust/ensogl/core/src/display/scene.rs b/lib/rust/ensogl/core/src/display/scene.rs index 177aa6aff0..ec4f240210 100644 --- a/lib/rust/ensogl/core/src/display/scene.rs +++ b/lib/rust/ensogl/core/src/display/scene.rs @@ -18,8 +18,8 @@ use crate::display::scene::dom::DomScene; use crate::display::shape::primitive::glsl; use crate::display::style; use crate::display::style::data::DataMatch; -use crate::display::symbol::registry::SymbolRegistry; use crate::display::symbol::Symbol; +use crate::display::world; use crate::system; use crate::system::gpu::data::uniform::Uniform; use crate::system::gpu::data::uniform::UniformScope; @@ -349,7 +349,7 @@ impl DomLayers { let canvas = web::document.create_canvas_or_panic(); canvas.set_style_or_warn("display", "block"); canvas.set_style_or_warn("z-index", "3"); - // These properties are set by `DomScene::new` constuctor for other layers. + // These properties are set by `DomScene::new` constructor for other layers. // See its documentation for more info. canvas.set_style_or_warn("position", "absolute"); canvas.set_style_or_warn("height", "100vh"); @@ -403,15 +403,12 @@ pub type SymbolRegistryDirty = dirty::SharedBool>; #[derive(Clone, CloneRef, Debug)] pub struct Dirty { - symbols: SymbolRegistryDirty, - shape: ShapeDirty, + shape: ShapeDirty, } impl Dirty { pub fn new(on_mut: OnMut) -> Self { - let shape = ShapeDirty::new(Box::new(on_mut.clone())); - let symbols = SymbolRegistryDirty::new(Box::new(on_mut)); - Self { symbols, shape } + Self { shape: ShapeDirty::new(Box::new(on_mut)) } } } @@ -703,7 +700,6 @@ pub struct SceneData { pub dom: Dom, pub context: Rc>>, pub context_lost_handler: Rc>>, - pub symbols: SymbolRegistry, pub variables: UniformScope, pub current_js_event: CurrentJsEvent, pub mouse: Mouse, @@ -737,17 +733,15 @@ impl SceneData { let display_mode = display_mode.clone_ref(); let dom = Dom::new(); let display_object = display::object::Root::new_named("Scene"); - let variables = UniformScope::new(); + let variables = world::with_context(|t| t.variables.clone_ref()); let dirty = Dirty::new(on_mut); - let symbols_dirty = &dirty.symbols; - let symbols = SymbolRegistry::mk(&variables, stats, f!(symbols_dirty.set())); - let layers = HardcodedLayers::new(); + let layers = world::with_context(|t| t.layers.clone_ref()); let stats = stats.clone(); let background = PointerTarget::new(); let pointer_target_registry = PointerTargetRegistry::new(&background); let uniforms = Uniforms::new(&variables); let renderer = Renderer::new(&dom, &variables); - let style_sheet = style::Sheet::new(); + let style_sheet = world::with_context(|t| t.style_sheet.clone_ref()); let current_js_event = CurrentJsEvent::new(); let frp = Frp::new(&dom.root.shape); let mouse = Mouse::new(&frp, &dom.root, &variables, ¤t_js_event, &display_mode); @@ -779,7 +773,6 @@ impl SceneData { dom, context, context_lost_handler, - symbols, variables, current_js_event, mouse, @@ -812,7 +805,7 @@ impl SceneData { /// restoration, after the context was lost. See the docs of [`Context`] to learn more. pub fn set_context(&self, context: Option<&Context>) { let _profiler = profiler::start_objective!(profiler::APP_LIFETIME, "@set_context"); - self.symbols.set_context(context); + world::with_context(|t| t.set_context(context)); *self.context.borrow_mut() = context.cloned(); self.dirty.shape.set(); self.renderer.set_context(context); @@ -827,11 +820,7 @@ impl SceneData { } pub fn new_symbol(&self) -> Symbol { - self.symbols.new() - } - - pub fn symbols(&self) -> &SymbolRegistry { - &self.symbols + world::with_context(|t| t.new()) } fn update_shape(&self) -> bool { @@ -850,13 +839,7 @@ impl SceneData { } fn update_symbols(&self) -> bool { - if self.dirty.symbols.check_all() { - self.symbols.update(); - self.dirty.symbols.unset_all(); - true - } else { - false - } + world::with_context(|context| context.update()) } fn update_camera(&self, scene: &Scene) -> bool { @@ -871,7 +854,7 @@ impl SceneData { if changed { was_dirty = true; self.frp.camera_changed_source.emit(()); - self.symbols.set_camera(&camera); + world::with_context(|t| t.set_camera(&camera)); self.dom.layers.front.update_view_projection(&camera); self.dom.layers.back.update_view_projection(&camera); } diff --git a/lib/rust/ensogl/core/src/display/scene/layer.rs b/lib/rust/ensogl/core/src/display/scene/layer.rs index aa24761a77..cd8fc30c56 100644 --- a/lib/rust/ensogl/core/src/display/scene/layer.rs +++ b/lib/rust/ensogl/core/src/display/scene/layer.rs @@ -7,7 +7,6 @@ use crate::data::dirty; use crate::data::OptVec; use crate::display; use crate::display::camera::Camera2d; -use crate::display::scene::Scene; use crate::display::shape::primitive::system::ShapeSystemFlavor; use crate::display::shape::system::Shape; use crate::display::shape::system::ShapeInstance; @@ -222,16 +221,10 @@ impl Layer { } /// Instantiate the provided [`ShapeProxy`]. - pub fn instantiate( - &self, - scene: &Scene, - data: &S::ShapeData, - ) -> (ShapeInstance, LayerShapeBinding) - where - S: Shape, - { + pub fn instantiate(&self, data: &S::ShapeData) -> (ShapeInstance, LayerShapeBinding) + where S: Shape { let (shape_system_info, symbol_id, shape_instance, global_instance_id) = - self.shape_system_registry.instantiate(scene, data); + self.shape_system_registry.instantiate(data); self.add_shape(shape_system_info, symbol_id); (shape_instance, LayerShapeBinding::new(self, global_instance_id)) } @@ -1157,9 +1150,9 @@ pub struct ShapeSystemRegistryData { impl { /// Instantiate the provided [`ShapeProxy`]. pub fn instantiate - (&mut self, scene:&Scene, data: &S::ShapeData) -> (ShapeSystemInfo, SymbolId, ShapeInstance, symbol::GlobalInstanceId) + (&mut self, data: &S::ShapeData) -> (ShapeSystemInfo, SymbolId, ShapeInstance, symbol::GlobalInstanceId) where S : Shape { - self.with_get_or_register_mut::(scene, data, |entry| { + self.with_get_or_register_mut::(data, |entry| { let system = entry.shape_system; let system_id = ShapeSystem::::id(); let (shape_instance, global_instance_id) = system.instantiate(); @@ -1219,7 +1212,6 @@ impl ShapeSystemRegistryData { // T: ShapeSystemInstance fn register( &mut self, - scene: &Scene, data: &S::ShapeData, ) -> ShapeSystemRegistryEntryRefMut> where @@ -1227,7 +1219,7 @@ impl ShapeSystemRegistryData { { let id = TypeId::of::(); let flavor = S::flavor(data); - let system = ShapeSystem::::new(scene, data); + let system = ShapeSystem::::new(data); let any = Box::new(system); let entry = ShapeSystemRegistryEntry { shape_system: any, instance_count: 0 }; self.shape_system_map.entry((id, flavor)).insert_entry(entry); @@ -1242,20 +1234,14 @@ impl ShapeSystemRegistryData { flavors.iter().map(|f| self.shape_system_map[&(system_id, *f)].instance_count).sum() } - fn with_get_or_register_mut( - &mut self, - scene: &Scene, - data: &S::ShapeData, - f: F, - ) -> Out + fn with_get_or_register_mut(&mut self, data: &S::ShapeData, f: F) -> Out where F: FnOnce(ShapeSystemRegistryEntryRefMut>) -> Out, - S: Shape, - { + S: Shape, { let flavor = S::flavor(data); match self.get_mut(flavor) { Some(entry) => f(entry), - None => f(self.register(scene, data)), + None => f(self.register(data)), } } } diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl b/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl index 53d6d6f9b7..eff9ac958b 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl +++ b/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl @@ -24,6 +24,11 @@ float alpha = shape.color.repr.raw.a; // =========================== float alpha_no_aa = alpha > ID_ALPHA_THRESHOLD ? 1.0 : 0.0; +// We need to set a default value for `output_id`, as shader optimizer may remove the following +// branching which results in a code that does never write to `output_id` shader output. Such a code +// is an invalid shader, as every shader has to have a code that writes to all outputs (even if it +// does not happen if some conditions are met). +output_id = vec4(0.0); if (pointer_events_enabled) { output_id = encode(input_global_instance_id,alpha_no_aa); } diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/style_watch.rs b/lib/rust/ensogl/core/src/display/shape/primitive/style_watch.rs index d21940d742..f2fff457ad 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/style_watch.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/style_watch.rs @@ -217,7 +217,11 @@ impl StyleWatch { /// Queries style sheet number value. Returns 0 if not found. pub fn get_number(&self, path: impl Into) -> f32 { - self.get_number_or(path, 0.0) + let path = path.into(); + self.get(path.clone()).number().unwrap_or_else(|| { + warn!("Tried to access undefined number from theme: {}", path); + 0.0 + }) } /// A debug check of how many stylesheet variables are registered in this style watch. diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/system.rs b/lib/rust/ensogl/core/src/display/shape/primitive/system.rs index 1a411ae8b9..c43406d1fe 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/system.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/system.rs @@ -65,7 +65,6 @@ use crate::system::gpu::types::*; use crate::display; use crate::display::object::instance::GenericLayoutApi; -use crate::display::scene::Scene; use crate::display::shape::primitive::shader; use crate::display::symbol; use crate::display::symbol::geometry::Sprite; @@ -102,6 +101,7 @@ pub trait Shape: 'static + Sized + AsRef { type GpuParams: Debug; type SystemData: CustomSystemData; type ShapeData: Debug; + fn definition_path() -> &'static str; fn pointer_events() -> bool; fn always_above() -> Vec; fn always_below() -> Vec; @@ -127,12 +127,11 @@ pub trait InstanceParamsTrait { /// such data. In case a shape system does not need any custom data, the empty tuple will be used. pub trait CustomSystemData { /// Constructor. - fn new(scene: &Scene, data: &ShapeSystemStandardData, shape_data: &S::ShapeData) -> Self; + fn new(data: &ShapeSystemStandardData, shape_data: &S::ShapeData) -> Self; } impl CustomSystemData for () { - fn new(_scene: &Scene, _data: &ShapeSystemStandardData, _shape_data: &S::ShapeData) -> Self { - } + fn new(_data: &ShapeSystemStandardData, _shape_data: &S::ShapeData) -> Self {} } @@ -261,14 +260,15 @@ impl ShapeSystem { /// Constructor. #[profile(Debug)] - pub fn new(scene: &Scene, shape_data: &S::ShapeData) -> Self { - let style_watch = display::shape::StyleWatch::new(&scene.style_sheet); + pub fn new(shape_data: &S::ShapeData) -> Self { + let style_watch = + display::world::with_context(|t| display::shape::StyleWatch::new(&t.style_sheet)); let shape_def = S::shape_def(&style_watch); let events = S::pointer_events(); - let model = display::shape::ShapeSystemModel::new(shape_def, events); + let model = display::shape::ShapeSystemModel::new(shape_def, events, S::definition_path()); let gpu_params = S::new_gpu_params(&model); let standard = ShapeSystemStandardData { gpu_params, model, style_watch }; - let user = CustomSystemData::::new(scene, &standard, shape_data); + let user = CustomSystemData::::new(&standard, shape_data); standard.model.init(); let data = Rc::new(ShapeSystemData { standard, user }); Self { data }.init_refresh_on_style_change() @@ -339,18 +339,20 @@ pub struct ShapeSystemModel { /// code. For example, the text system uses this field, as its material fully describes how to /// render glyphs. pub do_not_use_shape_definition: Rc>, + pub definition_path: Immutable<&'static str>, } impl ShapeSystemModel { /// Constructor. #[profile(Detail)] - pub fn new(shape: def::AnyShape, pointer_events: bool) -> Self { + pub fn new(shape: def::AnyShape, pointer_events: bool, definition_path: &'static str) -> Self { let sprite_system = SpriteSystem::new(); let material = Rc::new(RefCell::new(Self::default_material())); let geometry_material = Rc::new(RefCell::new(Self::default_geometry_material())); let pointer_events = Immutable(pointer_events); let shape = Rc::new(RefCell::new(shape)); let do_not_use_shape_definition = default(); + let definition_path = Immutable(definition_path); Self { sprite_system, shape, @@ -358,6 +360,7 @@ impl ShapeSystemModel { geometry_material, pointer_events, do_not_use_shape_definition, + definition_path, } } @@ -419,7 +422,7 @@ impl ShapeSystemModel { vec2 padded_size = input_size + padding2; vec2 uv_scale = padded_size / input_size; vec2 uv_offset = padding / input_size; - input_uv = vertex_uv * uv_scale - uv_offset; + input_uv = input_uv * uv_scale - uv_offset; // We need to recompute the vertex position with the padding. input_local = vec3((input_uv - input_alignment) * input_size, 0.0); @@ -439,9 +442,21 @@ impl ShapeSystemModel { /// Generates the shape again. It is called on shape definition change, e.g. after theme update. fn reload_shape(&self) { - if !self.do_not_use_shape_definition.get() { - let code = shader::builder::Builder::run(&*self.shape.borrow(), *self.pointer_events); + if let Some(shader) = crate::display::world::PRECOMPILED_SHADERS + .with_borrow(|map| map.get(*self.definition_path).cloned()) + { + let code = crate::display::shader::builder::CodeTemplate::new("", shader.fragment, ""); self.material.borrow_mut().set_code(code); + } else { + if !display::world::with_context(|t| t.run_mode.get().is_shader_extraction()) { + let path = *self.definition_path; + warn!("No precompiled shader found for '{path}'. This will affect performance."); + } + if !self.do_not_use_shape_definition.get() { + let code = + shader::builder::Builder::run(&*self.shape.borrow(), *self.pointer_events); + self.material.borrow_mut().set_code(code); + } } self.reload_material(); } @@ -549,9 +564,37 @@ macro_rules! shape { }; } +// FIXME[WD]: This macro was left in the code because glyphs are not able to use the shader +// precompilation pipeline. It will be removed in the next PR: +// https://www.pivotaltracker.com/story/show/184304289 +/// Defines a new shape system. This is the macro that you want to use to define new shapes. The +/// shapes will be automatically managed in a highly efficient manner by the [`ShapeSystem`]. +#[macro_export] +macro_rules! shape_old { + ( + $(type SystemData = $system_data:ident;)? + $(type ShapeData = $shape_data:ident;)? + $(flavor = $flavor:path;)? + $(above = [$($always_above_1:tt $(::$always_above_2:tt)*),*];)? + $(below = [$($always_below_1:tt $(::$always_below_2:tt)*),*];)? + $(pointer_events = $pointer_events:tt;)? + ($style:ident : Style $(,$gpu_param : ident : $gpu_param_type : ty)* $(,)?) {$($body:tt)*} + ) => { + $crate::_shape_old! { + $(SystemData($system_data))? + $(ShapeData($shape_data))? + $(flavor = [$flavor];)? + $(above = [$($always_above_1 $(::$always_above_2)*),*];)? + $(below = [$($always_below_1 $(::$always_below_2)*),*];)? + $(pointer_events = $pointer_events;)? + [$style] ($($gpu_param : $gpu_param_type),*){$($body)*} + } + }; +} + /// Internal helper for the [`shape`] macro. #[macro_export] -macro_rules! _shape { +macro_rules! _shape_old { ( $(SystemData($system_data:ident))? $(ShapeData($shape_data:ident))? @@ -604,6 +647,11 @@ macro_rules! _shape { type GpuParams = GpuParams; type SystemData = ($($system_data)?); type ShapeData = ($($shape_data)?); + + fn definition_path() -> &'static str { + root_call_path!() + } + fn pointer_events() -> bool { let _out = true; $(let _out = $pointer_events;)? @@ -687,6 +735,170 @@ macro_rules! _shape { } + // ============ + // === View === + // ============ + + /// A view of the defined shape. You can place the view in your objects and it will + /// automatically initialize on-demand. + pub type View = $crate::gui::component::ShapeView; + } + }; +} + + +/// Internal helper for the [`shape`] macro. +#[macro_export] +macro_rules! _shape { + ( + $(SystemData($system_data:ident))? + $(ShapeData($shape_data:ident))? + $(flavor = [$flavor:path];)? + $(above = [$($always_above_1:tt $(::$always_above_2:tt)*),*];)? + $(below = [$($always_below_1:tt $(::$always_below_2:tt)*),*];)? + $(pointer_events = $pointer_events:tt;)? + [$style:ident] + ($($gpu_param : ident : $gpu_param_type : ty),* $(,)?) + {$($body:tt)*} + ) => { + + pub use shape_system_definition::Shape; + pub use shape_system_definition::View; + + #[allow(unused_qualifications)] + #[allow(unused_imports)] + mod shape_system_definition { + use super::*; + use $crate::prelude::*; + use $crate::display; + use $crate::display::symbol::geometry::Sprite; + use $crate::system::gpu; + use $crate::system::gpu::data::Attribute; + use $crate::display::shape::ShapeSystemId; + use $crate::display::shape::ShapeOps; + use $crate::display::shape::PixelDistance; + use $crate::display::shape::system::ProxyParam; + use $crate::system::gpu::data::InstanceIndex; + use $crate::display::shape::system::*; + + + // ============= + // === Shape === + // ============= + + /// The type of the shape. It also contains the parameters of the shape. The parameters + /// are stored in this type in order to simplify bounds for utlities managing shape + /// systems. For example, if we would like to handle any shape with given parameters, + /// we will be processing [`ShapeSystem`] and we can add bounds to [`S`] to reflect + /// what parameters it should contain. + #[allow(missing_docs)] + #[derive(AsRef, Debug, Deref)] + pub struct Shape { + pub params: InstanceParams, + } + + impl $crate::display::shape::system::Shape for Shape { + type InstanceParams = InstanceParams; + type GpuParams = GpuParams; + type SystemData = ($($system_data)?); + type ShapeData = ($($shape_data)?); + + fn definition_path() -> &'static str { + root_call_path!() + } + + fn pointer_events() -> bool { + let _out = true; + $(let _out = $pointer_events;)? + _out + } + + fn always_above() -> Vec { + vec![$($( ShapeSystem::<$always_above_1 $(::$always_above_2)*::Shape>::id() ),*)?] + } + + fn always_below() -> Vec { + vec![$($( + ShapeSystem::<$always_below_1 $(::$always_below_2)*::Shape> :: id() + ),*)?] + } + + fn new_instance_params( + gpu_params:&Self::GpuParams, + id: InstanceIndex + ) -> Shape { + $(let $gpu_param = ProxyParam::new(gpu_params.$gpu_param.at(id));)* + let params = Self::InstanceParams { $($gpu_param),* }; + Shape { params } + } + + fn new_gpu_params( + shape_system: &display::shape::ShapeSystemModel + ) -> Self::GpuParams { + $( + let name = stringify!($gpu_param); + let val = gpu::data::default::gpu_default::<$gpu_param_type>(); + let $gpu_param = shape_system.add_input(name,val); + )* + Self::GpuParams {$($gpu_param),*} + } + + fn shape_def(__style_watch__: &display::shape::StyleWatch) + -> display::shape::primitive::def::AnyShape { + #[allow(unused_imports)] + use $crate::display::style::data::DataMatch; + use $crate::data::color; + use $crate::display::shape::*; + + __style_watch__.reset(); + let $style = __style_watch__; + // Silencing warnings about not used style. + let _unused = &$style; + $( + let $gpu_param : $crate::display::shape::primitive::def::Var<$gpu_param_type> = + concat!("input_",stringify!($gpu_param)).into(); + // Silencing warnings about not used shader input variables. + let _unused = &$gpu_param; + )* + $($body)* + } + + $(fn flavor(data: &Self::ShapeData) -> $crate::display::shape::system::ShapeSystemFlavor { + $flavor(data) + })? + } + + /// Register the shape definition in the global shape registry. It is used for shader + /// compilation during build. + #[before_main] + pub fn register_shape() { + $crate::display::world::SHAPES_DEFINITIONS.with(|shapes| { + shapes.borrow_mut().push(Box::new(|| Box::new(View::new()))); + }); + } + + /// An initialized, GPU-bound shape definition. All changed parameters are immediately + /// reflected in the [`Buffer`] and will be synchronised with GPU before next frame is + /// drawn. + #[derive(Debug)] + #[allow(missing_docs)] + pub struct InstanceParams { + $(pub $gpu_param : ProxyParam>),* + } + + impl InstanceParamsTrait for InstanceParams { + fn swap(&self, other: &Self) { + $(self.$gpu_param.swap(&other.$gpu_param);)* + } + } + + #[derive(Clone, CloneRef, Debug)] + #[allow(missing_docs)] + pub struct GpuParams { + $(pub $gpu_param: gpu::data::Buffer<$gpu_param_type>),* + } + + // ============ // === View === // ============ diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu.rs b/lib/rust/ensogl/core/src/display/symbol/gpu.rs index 5b70141e06..c4b756ce71 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu.rs @@ -409,17 +409,15 @@ impl Symbol { &self, global_variables: &UniformScope, ) -> Vec { - let var_decls = self.shader.collect_variables(); - var_decls - .into_iter() - .map(|(var_name, var_decl)| { - let target = self.lookup_variable(&var_name, global_variables); - if target.is_none() { - warn!("Unable to bind variable '{}' to geometry buffer.", var_name); - } - shader::VarBinding::new(var_name, var_decl, target) - }) - .collect() + let mut vars = self.shader.collect_variables(); + for binding in &mut vars { + let scope = self.lookup_variable(&binding.name, global_variables); + if scope.is_none() { + warn!("Unable to bind variable '{}' to geometry buffer.", binding.name); + } + binding.scope = scope; + } + vars } /// Runs the provided function in a context of active program and active VAO. After the function diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs index 39f6227528..7d3151d8a3 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/geometry/compound/sprite.rs @@ -14,6 +14,7 @@ use crate::display::symbol::material::Material; use crate::display::symbol::Symbol; use crate::display::symbol::SymbolId; use crate::display::symbol::SymbolInstance; +use crate::display::world; @@ -197,7 +198,7 @@ pub struct SpriteModel { #[deref] pub instance: SymbolInstance, pub symbol: Symbol, - pub size: SizedObject, + size: SizedObject, transform: Attribute>, stats: SpriteStats, erase_on_drop: EraseOnDrop>>, @@ -288,9 +289,7 @@ impl SpriteSystem { /// Constructor. #[profile(Detail)] pub fn new() -> Self { - let scene = scene(); - let stats = scene.stats.clone_ref(); - let symbol = scene.new_symbol(); + let (stats, symbol) = world::with_context(|t| (t.stats.clone_ref(), t.new())); let mesh = symbol.surface(); let point_scope = mesh.point_scope(); let instance_scope = mesh.instance_scope(); diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs index 15e8bea3e3..b9572c862c 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/registry.rs @@ -3,10 +3,15 @@ use crate::data::dirty::traits::*; use crate::prelude::*; +use crate::system::web::traits::*; use crate::data::dirty; +use crate::debug; use crate::debug::stats::Stats; use crate::display::camera::Camera2d; +use crate::display::scene; +use crate::display::style; +use crate::display::style::theme; use crate::display::symbol; use crate::display::symbol::RenderGroup; use crate::display::symbol::Symbol; @@ -15,6 +20,7 @@ use crate::display::symbol::WeakSymbol; use crate::system::gpu::data::uniform::Uniform; use crate::system::gpu::data::uniform::UniformScope; use crate::system::gpu::Context; +use crate::system::web; @@ -22,9 +28,36 @@ use crate::system::gpu::Context; // === Types === // ============= -pub type SymbolDirty = dirty::SharedSet>; +pub type Dirty = dirty::SharedSet; +// =============== +// === RunMode === +// =============== + +/// The application run mode. It can be set to either [`Normal`] or [`ShaderExtraction`] mode. In +/// the latter case some warnings are suppressed. For example, there will be no warning that +/// precompiled shapes shaders were not found, as they are yet to be generated. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +#[allow(missing_docs)] +pub enum RunMode { + #[default] + Normal, + ShaderExtraction, +} + +impl RunMode { + /// Check whether the mode is set to [`Normal`]. + pub fn is_normal(self) -> bool { + self == Self::Normal + } + + /// Check whether the mode is set to [`ShaderExtraction`]. + pub fn is_shader_extraction(self) -> bool { + self == Self::ShaderExtraction + } +} + // ====================== // === SymbolRegistry === @@ -50,56 +83,64 @@ pub type SymbolDirty = dirty::SharedSet>; /// general-purpose weak reference is used here because it's a well-known abstraction. #[derive(Clone, CloneRef, Debug)] pub struct SymbolRegistry { + pub run_mode: Rc>, symbols: Rc>>, global_id_provider: symbol::GlobalInstanceIdProvider, - symbol_dirty: SymbolDirty, + pub dirty: Dirty, view_projection: Uniform>, z_zoom_1: Uniform, - variables: UniformScope, + pub variables: UniformScope, context: Rc>>, - stats: Stats, + pub stats: Stats, next_id: Rc>, + pub style_sheet: style::Sheet, + pub theme_manager: theme::Manager, + pub layers: scene::HardcodedLayers, } impl SymbolRegistry { /// Constructor. - pub fn mk( - variables: &UniformScope, - stats: &Stats, - on_mut: OnMut, - ) -> Self { + pub fn mk() -> Self { debug!("Initializing."); - let symbol_dirty = SymbolDirty::new(Box::new(on_mut)); + let run_mode = default(); + let dirty = Dirty::new(()); let symbols = default(); - let variables = variables.clone(); + let variables = UniformScope::new(); let view_projection = variables.add_or_panic("view_projection", Matrix4::::identity()); let z_zoom_1 = variables.add_or_panic("z_zoom_1", 1.0); let context = default(); - let stats = stats.clone_ref(); + let stats = debug::stats::Stats::new(web::window.performance_or_panic()); let global_id_provider = default(); let next_id = default(); + let style_sheet = style::Sheet::new(); + let theme_manager = theme::Manager::from(&style_sheet); + let layers = scene::HardcodedLayers::new(); Self { + run_mode, symbols, global_id_provider, - symbol_dirty, + dirty, view_projection, z_zoom_1, variables, context, stats, next_id, + style_sheet, + theme_manager, + layers, } } /// Creates a new `Symbol`. #[allow(clippy::new_ret_no_self)] pub fn new(&self) -> Symbol { - let symbol_dirty = self.symbol_dirty.clone(); + let dirty = self.dirty.clone(); let stats = &self.stats; let id_value = self.next_id.get(); self.next_id.set(id_value + 1); let id = SymbolId::new(id_value); - let on_mut = move || symbol_dirty.set(id); + let on_mut = move || dirty.set(id); let symbol = Symbol::new(stats, id, &self.global_id_provider, on_mut); symbol.set_context(self.context.borrow().as_ref()); self.symbols.borrow_mut().insert(id, symbol.clone_ref()); @@ -116,16 +157,21 @@ impl SymbolRegistry { } /// Check dirty flags and update the state accordingly. - pub fn update(&self) { - debug_span!("Updating.").in_scope(|| { - let symbols = self.symbols.borrow(); - for id in self.symbol_dirty.take().iter() { - if let Some(symbol) = symbols.get(id) { - symbol.update(&self.variables); + pub fn update(&self) -> bool { + if self.dirty.check_all() { + debug_span!("Updating.").in_scope(|| { + let symbols = self.symbols.borrow(); + for id in self.dirty.take().iter() { + if let Some(symbol) = symbols.get(id) { + symbol.update(&self.variables); + } } - } - self.symbol_dirty.unset_all(); - }) + self.dirty.unset_all(); + }); + true + } else { + false + } } /// Updates the view-projection matrix after camera movement. diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs index 1880252295..04440ceb97 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/shader.rs @@ -119,48 +119,67 @@ impl { } } + /// Get the shader code in GLSL 310 format. The shader parameters will not be bound to any + /// particular mesh and thus this code can be used for optimization purposes only. + pub fn abstract_shader_code_in_glsl_310(&self) -> crate::system::gpu::shader::Code { + let bindings = self.collect_variables().into_iter().map(|mut binding| { + binding.scope = Some(ScopeType::Mesh(crate::display::symbol::geometry::primitive::mesh::ScopeType::Instance)); + binding + }).collect_vec(); + self.gen_gpu_code(glsl::Version::V310, &bindings) + } + + /// Generate the final GLSL code based on the provided bindings. + pub fn gen_gpu_code( + &self, + glsl_version: glsl::Version, + bindings:&[VarBinding] + ) -> crate::system::gpu::shader::Code { + debug_span!("Generating GPU code.").in_scope(|| { + let mut shader_cfg = builder::ShaderConfig::new(glsl_version); + let mut shader_builder = builder::ShaderBuilder::new(glsl_version); + + for binding in bindings { + let name = &binding.name; + let tp = &binding.decl.tp; + match binding.scope { + None => { + warn!("Default shader values are not implemented yet. \ + This will cause visual glitches."); + shader_cfg.add_uniform(name,tp); + }, + Some(scope_type) => match scope_type { + ScopeType::Symbol => shader_cfg.add_uniform (name,tp), + ScopeType::Global => shader_cfg.add_uniform (name,tp), + _ => shader_cfg.add_attribute (name,tp), + } + } + } + + self.geometry_material.outputs().iter().for_each(|(name,decl)|{ + shader_cfg.add_shared_attribute(name,&decl.tp); + }); + + shader_cfg.add_output("color", glsl::PrimType::Vec4); + self.surface_material.outputs().iter().for_each(|(name,decl)|{ + shader_cfg.add_output(name,&decl.tp); + }); + + let vertex_code = self.geometry_material.code().clone(); + let fragment_code = self.surface_material.code().clone(); + shader_builder.compute(&shader_cfg, vertex_code, fragment_code); + shader_builder.build() + }) + } + /// Check dirty flags and update the state accordingly. pub fn update (&mut self, bindings:Vec, on_ready:F) { debug_span!("Updating.").in_scope(|| { if let Some(context) = &self.context { if self.dirty.check_all() { - self.stats.inc_shader_compile_count(); - - let mut shader_cfg = builder::ShaderConfig::new(); - let mut shader_builder = builder::ShaderBuilder::new(); - - for binding in &bindings { - let name = &binding.name; - let tp = &binding.decl.tp; - match binding.scope { - None => { - warn!("Default shader values are not implemented yet. \ - This will cause visual glitches."); - shader_cfg.add_uniform(name,tp); - }, - Some(scope_type) => match scope_type { - ScopeType::Symbol => shader_cfg.add_uniform (name,tp), - ScopeType::Global => shader_cfg.add_uniform (name,tp), - _ => shader_cfg.add_attribute (name,tp), - } - } - } - - self.geometry_material.outputs().iter().for_each(|(name,decl)|{ - shader_cfg.add_shared_attribute(name,&decl.tp); - }); - - shader_cfg.add_output("color", glsl::PrimType::Vec4); - self.surface_material.outputs().iter().for_each(|(name,decl)|{ - shader_cfg.add_output(name,&decl.tp); - }); - - let vertex_code = self.geometry_material.code().clone(); - let fragment_code = self.surface_material.code().clone(); - shader_builder.compute(&shader_cfg, vertex_code, fragment_code); - let code = shader_builder.build(); + let code = self.gen_gpu_code(glsl::Version::V300, &bindings); *self.program.borrow_mut() = None; let program = self.program.clone_ref(); @@ -183,10 +202,13 @@ impl { } /// Traverses the shader definition and collects all attribute names. - pub fn collect_variables(&self) -> BTreeMap { - let geometry_material_inputs = self.geometry_material.inputs().clone(); - let surface_material_inputs = self.surface_material.inputs().clone(); - geometry_material_inputs.into_iter().chain(surface_material_inputs).collect() + pub fn collect_variables(&self) -> Vec { + let mut out = vec![]; + let vars = self.geometry_material.inputs().iter().chain(self.surface_material.inputs()); + for (name,decl) in vars { + out.push(VarBinding::new(name.clone(), decl.clone(), None)); + } + out } }} diff --git a/lib/rust/ensogl/core/src/display/symbol/gpu/shader/builder.rs b/lib/rust/ensogl/core/src/display/symbol/gpu/shader/builder.rs index d1053d9788..2418d00a35 100644 --- a/lib/rust/ensogl/core/src/display/symbol/gpu/shader/builder.rs +++ b/lib/rust/ensogl/core/src/display/symbol/gpu/shader/builder.rs @@ -11,22 +11,136 @@ use std::collections::BTreeMap; +// ============================== +// === Shader Parameter Types === +// ============================== + +/// Shader attribute marker type. +#[derive(Clone, Copy, Debug, Default)] +pub struct Attribute; + +/// Shader uniform marker type. +#[derive(Clone, Copy, Debug, Default)] +pub struct Uniform; + + + +// ========================== +// === ScopeLayoutManager === +// ========================== + +/// A struct that tracks which layout slots were used per scope types. This is used to generate GLSL +/// code annotated with explicit uniforms and attributes locations. +/// +/// Please note that WebGL is using GLSL 300 ES, which does not allow for explicit uniform locations +/// and requires explicit fragment output attributes locations. However, GLSL optimizer (`shaderc`) +/// supports GLSL 310+ and requires explicit layout location of all inputs. The code generated with +/// all locations is used by the optimizer only and the original body is replaced with the optimized +/// one in the end. +#[derive(Clone, Copy, Debug)] +#[allow(missing_docs)] +pub struct LayoutManager { + pub uniforms: ScopeLayoutManager, + pub vert_in: ScopeLayoutManager, + pub vert_out: ScopeLayoutManager, + pub frag_in: ScopeLayoutManager, + pub frag_out: ScopeLayoutManager, +} + +/// Configuration of [`LayoutManager`]. Used to decide which shader parameters should be generated +/// with explicit location information. +#[derive(Clone, Copy, Debug)] +#[allow(missing_docs)] +pub struct LayoutEnabledFor { + pub uniforms: bool, + pub vert_in: bool, + pub vert_out: bool, + pub frag_in: bool, + pub frag_out: bool, +} + +impl LayoutManager { + /// Constructor. The argument defines whether the locations should be used. + pub fn new(enabled: LayoutEnabledFor) -> Self { + Self { + uniforms: ScopeLayoutManager::new(enabled.uniforms), + vert_in: ScopeLayoutManager::new(enabled.vert_in), + vert_out: ScopeLayoutManager::new(enabled.vert_out), + frag_in: ScopeLayoutManager::new(enabled.frag_in), + frag_out: ScopeLayoutManager::new(enabled.frag_out), + } + } +} + +/// A struct that tracks which layout slots were used per scope types. Internal representation of +/// [`LayoutManager`]. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug)] +pub struct ScopeLayoutManager { + location: Option, + tp: PhantomData, +} + +impl ScopeLayoutManager { + /// Constructor. + pub fn new(enabled: bool) -> Self { + Self { location: enabled.as_some(0), tp: default() } + } +} + +/// Find the first free location for the given attribute type. +#[allow(missing_docs)] +pub trait ScopeLayoutManagerAttributePlacement { + fn place(&mut self, tp: &glsl::Type) -> Option; +} + +impl ScopeLayoutManagerAttributePlacement for ScopeLayoutManager { + fn place(&mut self, tp: &glsl::Type) -> Option { + let location = self.location?; + self.location = Some(location + tp.layout_size()); + Some(glsl::Layout { location }) + } +} + +/// Find the first free location for a uniform. No matter the type, uniforms always take a +/// single location slot. +#[allow(missing_docs)] +pub trait ScopeLayoutManagerUniformPlacement { + fn place(&mut self) -> Option; +} + +impl ScopeLayoutManagerUniformPlacement for ScopeLayoutManager { + fn place(&mut self) -> Option { + let location = self.location?; + self.location = Some(location + 1); + Some(glsl::Layout { location }) + } +} + + + // ==================== // === ShaderConfig === // ==================== -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct ShaderConfig { - pub precision: ShaderPrecision, - pub outputs: BTreeMap, - pub shared: BTreeMap, - pub attributes: BTreeMap, - pub uniforms: BTreeMap, + pub glsl_version: glsl::Version, + pub precision: ShaderPrecision, + pub outputs: BTreeMap, + pub shared: BTreeMap, + pub attributes: BTreeMap, + pub uniforms: BTreeMap, } impl ShaderConfig { - pub fn new() -> Self { - default() + pub fn new(glsl_version: glsl::Version) -> Self { + let precision = default(); + let outputs = default(); + let shared = default(); + let attributes = default(); + let uniforms = default(); + Self { glsl_version, precision, outputs, shared, attributes, uniforms } } pub fn add_attribute>(&mut self, name: S, qual: Q) { @@ -104,29 +218,36 @@ impl AttributeQualifier { pub fn to_input_var>( &self, name: Name, + layout_manager: &mut ScopeLayoutManager, use_qual: bool, ) -> glsl::GlobalVar { + let layout = layout_manager.place(&self.typ); let mut storage = self.storage; if !use_qual { storage.interpolation = None; } glsl::GlobalVar { - layout: None, + layout, storage: Some(glsl::GlobalVarStorage::InStorage(storage)), - prec: self.prec, - typ: self.typ.clone(), - ident: name.into(), + prec: self.prec, + typ: self.typ.clone(), + ident: name.into(), } } - pub fn to_output_var>(&self, name: Name) -> glsl::GlobalVar { + pub fn to_output_var>( + &self, + name: Name, + layout_manager: &mut ScopeLayoutManager, + ) -> glsl::GlobalVar { + let layout = layout_manager.place(&self.typ); let storage = self.storage; glsl::GlobalVar { - layout: None, + layout, storage: Some(glsl::GlobalVarStorage::OutStorage(storage)), - prec: self.prec, - typ: self.typ.clone(), - ident: name.into(), + prec: self.prec, + typ: self.typ.clone(), + ident: name.into(), } } } @@ -140,6 +261,18 @@ impl From for AttributeQualifier { interpolation: Some(glsl::InterpolationStorage::Flat), ..default() }, + glsl::PrimType::IVec2 => glsl::LinkageStorage { + interpolation: Some(glsl::InterpolationStorage::Flat), + ..default() + }, + glsl::PrimType::IVec3 => glsl::LinkageStorage { + interpolation: Some(glsl::InterpolationStorage::Flat), + ..default() + }, + glsl::PrimType::IVec4 => glsl::LinkageStorage { + interpolation: Some(glsl::InterpolationStorage::Flat), + ..default() + }, _ => default(), }; Self { storage, prec, typ } @@ -170,13 +303,18 @@ pub struct UniformQualifier { } impl UniformQualifier { - pub fn to_var>(&self, name: Name) -> glsl::GlobalVar { + pub fn to_var>( + &self, + name: Name, + layout_manager: &mut ScopeLayoutManager, + ) -> glsl::GlobalVar { + let layout = layout_manager.place(); glsl::GlobalVar { - layout: None, + layout, storage: Some(glsl::GlobalVarStorage::UniformStorage), - prec: self.prec, - typ: self.typ.clone(), - ident: name.into(), + prec: self.prec, + typ: self.typ.clone(), + ident: name.into(), } } } @@ -272,15 +410,26 @@ impl CodeTemplate { // === ShaderBuilder === // ===================== -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct ShaderBuilder { + pub layout: LayoutManager, pub vertex: glsl::Module, pub fragment: glsl::Module, } impl ShaderBuilder { - pub fn new() -> Self { - default() + pub fn new(glsl_version: glsl::Version) -> Self { + let layout_required = glsl_version.requires_layout(); + let layout = LayoutManager::new(LayoutEnabledFor { + uniforms: layout_required, + vert_in: layout_required, + vert_out: layout_required, + frag_in: layout_required, + frag_out: true, + }); + let vertex = glsl::Module::new(glsl_version); + let fragment = glsl::Module::new(glsl_version); + Self { layout, vertex, fragment } } pub fn compute( @@ -320,9 +469,9 @@ impl ShaderBuilder { let vert_name = mk_vertex_name(name); let frag_name = mk_fragment_name(name); let sharing = glsl::Assignment::new(&frag_name, &vert_name); - self.vertex.add(qual.to_input_var(&vert_name, false)); - self.vertex.add(qual.to_output_var(&frag_name)); - self.fragment.add(qual.to_input_var(&frag_name, true)); + self.vertex.add(qual.to_input_var(&vert_name, &mut self.layout.vert_in, false)); + self.vertex.add(qual.to_output_var(&frag_name, &mut self.layout.vert_out)); + self.fragment.add(qual.to_input_var(&frag_name, &mut self.layout.frag_in, true)); self.vertex.main.add(sharing); } } @@ -332,8 +481,8 @@ impl ShaderBuilder { if !cfg.shared.is_empty() { for (name, qual) in &cfg.shared { let frag_name = mk_fragment_name(name); - self.vertex.add(qual.to_output_var(&frag_name)); - self.fragment.add(qual.to_input_var(&frag_name, true)); + self.vertex.add(qual.to_output_var(&frag_name, &mut self.layout.vert_out)); + self.fragment.add(qual.to_input_var(&frag_name, &mut self.layout.frag_in, true)); } } } @@ -342,20 +491,17 @@ impl ShaderBuilder { if !cfg.uniforms.is_empty() { for (name, qual) in &cfg.uniforms { let name = mk_uniform_name(name); - self.vertex.add(qual.to_var(&name)); - self.fragment.add(qual.to_var(&name)); + let var = qual.to_var(&name, &mut self.layout.uniforms); + self.vertex.add(var.clone()); + self.fragment.add(var); } } } fn gen_outputs_code(&mut self, cfg: &ShaderConfig) { - if !cfg.outputs.is_empty() { - cfg.outputs.iter().enumerate().for_each(|(loc, (name, qual))| { - let name = mk_out_name(name); - let mut var = qual.to_output_var(name); - var.layout = Some(glsl::Layout { location: loc }); - self.fragment.add(var); - }); + for (name, qual) in &cfg.outputs { + let name = mk_out_name(name); + self.fragment.add(qual.to_output_var(name, &mut self.layout.frag_out)); } } diff --git a/lib/rust/ensogl/core/src/display/world.rs b/lib/rust/ensogl/core/src/display/world.rs index 1ca0cf08e7..1dcbe3ae18 100644 --- a/lib/rust/ensogl/core/src/display/world.rs +++ b/lib/rust/ensogl/core/src/display/world.rs @@ -6,6 +6,7 @@ use crate::data::dirty::traits::*; use crate::display::render::*; use crate::prelude::*; use crate::system::web::traits::*; +use wasm_bindgen::prelude::*; use crate::animation; use crate::application::command::FrpNetworkProvider; @@ -21,6 +22,10 @@ use crate::display::render::passes::SymbolsRenderPass; use crate::display::scene::DomPath; use crate::display::scene::Scene; use crate::display::shape::primitive::glsl; +use crate::display::symbol::registry::RunMode; +use crate::display::symbol::registry::SymbolRegistry; +use crate::system::gpu::shader; +use crate::system::js; use crate::system::web; use enso_types::unit2::Duration; @@ -37,6 +42,141 @@ pub use crate::display::symbol::types::*; +// =============== +// === Context === +// =============== + +thread_local! { + /// A global object containing registry of all symbols. In the future, it will be extended to + /// contain buffers and other elements that are now kept in `Rc>` in different + /// places. + pub static CONTEXT: RefCell> = RefCell::new(None); +} + +/// Perform an action with a reference to the global context. +pub fn with_context(f: impl Fn(&SymbolRegistry) -> T) -> T { + CONTEXT.with_borrow(|t| f(t.as_ref().unwrap())) +} + +/// Initialize global state (set stack trace size, logger output, etc). +#[before_main(0)] +pub fn init() { + init_global(); +} + +/// Initialize global context. +#[before_main(1)] +pub fn wasm_init_context() { + init_context(); +} + +fn init_context() { + let initialized = CONTEXT.with(|t| t.borrow().is_some()); + if !initialized { + CONTEXT.with_borrow_mut(|t| *t = Some(SymbolRegistry::mk())); + } +} + + + +// ===================== +// === Static Shapes === +// ===================== + +type ShapeCons = Box Box>; + +thread_local! { + /// All shapes defined with the `shape!` macro. They will be populated on the beginning of + /// program execution, before the `main` function is called. + pub static SHAPES_DEFINITIONS: RefCell> = default(); +} + + + +// =========================== +// === Precompiled Shaders === +// =========================== + +/// A precompiled shader definition. It contains the main function body for the vertex and fragment +/// shaders. +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub struct PrecompiledShader { + pub vertex: String, + pub fragment: String, +} + +thread_local! { + /// List of all precompiled shaders. They are registered here before main entry point is run by + /// the EnsoGL runner in JavaScript. + pub static PRECOMPILED_SHADERS: RefCell> = default(); +} + +/// Registers in JS a closure to acquire non-optimized shaders code and to set back optimized +/// shaders code. +#[before_main] +pub fn register_get_and_set_shaders_fns() { + let js_app = js::app_or_panic(); + let closure = Closure::new(|| { + let map = gather_shaders(); + let js_map = web::Map::new(); + for (key, code) in map { + let value = web::Object::new(); + web::Reflect::set(&value, &"vertex".into(), &code.vertex.into()).unwrap(); + web::Reflect::set(&value, &"fragment".into(), &code.fragment.into()).unwrap(); + js_map.set(&key.into(), &value); + } + js_map.into() + }); + js_app.register_get_shaders_rust_fn(&closure); + mem::forget(closure); + + let closure = Closure::new(|value: JsValue| { + if extract_shaders_from_js(value).err().is_some() { + warn!("Internal error. Downloaded shaders are provided in a wrong format.") + } + }); + js_app.register_set_shaders_rust_fn(&closure); + mem::forget(closure); +} + +/// Extract optimized shaders code from the JS value. +fn extract_shaders_from_js(value: JsValue) -> Result<(), JsValue> { + let map = value.dyn_into::()?; + for opt_entry in map.entries() { + let entry = opt_entry?.dyn_into::()?; + let key: String = entry.get(0).dyn_into::()?.into(); + let value = entry.get(1).dyn_into::()?; + let vertex_field = web::Reflect::get(&value, &"vertex".into())?; + let fragment_field = web::Reflect::get(&value, &"fragment".into())?; + let vertex: String = vertex_field.dyn_into::()?.into(); + let fragment: String = fragment_field.dyn_into::()?.into(); + let precompiled_shader = PrecompiledShader { vertex, fragment }; + debug!("Registering precompiled shaders for '{key}'."); + PRECOMPILED_SHADERS.with_borrow_mut(move |map| { + map.insert(key, precompiled_shader); + }); + } + Ok(()) +} + +fn gather_shaders() -> HashMap<&'static str, shader::Code> { + with_context(|t| t.run_mode.set(RunMode::ShaderExtraction)); + let mut map = HashMap::new(); + SHAPES_DEFINITIONS.with(|shapes| { + for shape_cons in shapes.borrow().iter() { + let shape = shape_cons(); + let path = shape.definition_path(); + let code = shape.abstract_shader_code_in_glsl_310(); + map.insert(path, code); + } + }); + with_context(|t| t.run_mode.set(RunMode::Normal)); + map +} + + + // ================ // === Uniforms === // ================ @@ -155,6 +295,9 @@ pub struct WorldDataWithLoop { impl WorldDataWithLoop { /// Constructor. pub fn new() -> Self { + // Context is initialized automatically before main entry point starts in WASM. We are + // performing manual initialization for native tests to work correctly. + init_context(); let frp = Frp::new(); let data = WorldData::new(&frp.private.output); let on_frame_start = animation::on_frame_start(); @@ -236,6 +379,7 @@ pub struct WorldData { stats_draw_handle: callback::Handle, pub on: Callbacks, debug_hotkeys_handle: Rc>>, + update_themes_handle: callback::Handle, garbage_collector: garbage::Collector, emit_measurements_handle: Rc>>, } @@ -244,7 +388,7 @@ impl WorldData { /// Create and initialize new world instance. pub fn new(frp: &api::private::Output) -> Self { let frp = frp.clone_ref(); - let stats = debug::stats::Stats::new(web::window.performance_or_panic()); + let stats = Stats::new(web::window.performance_or_panic()); let stats_monitor = debug::monitor::Monitor::new(); let on = Callbacks::default(); let scene_dirty = dirty::SharedBool::new(()); @@ -258,8 +402,9 @@ impl WorldData { stats_monitor.sample_and_draw(stats); log_render_stats(*stats) })); + let themes = with_context(|t| t.theme_manager.clone_ref()); + let update_themes_handle = on.before_frame.add(f_!(themes.update())); let emit_measurements_handle = default(); - SCENE.with_borrow_mut(|t| *t = Some(default_scene.clone_ref())); Self { @@ -273,6 +418,7 @@ impl WorldData { debug_hotkeys_handle, stats_monitor, stats_draw_handle, + update_themes_handle, garbage_collector, emit_measurements_handle, } @@ -346,11 +492,7 @@ impl WorldData { // pixel_read_pass.set_threshold(1); let logger = Logger::new("renderer"); let pipeline = render::Pipeline::new() - .add(SymbolsRenderPass::new( - logger, - self.default_scene.symbols(), - &self.default_scene.layers, - )) + .add(SymbolsRenderPass::new(logger, &self.default_scene.layers)) .add(ScreenRenderPass::new()) .add(pixel_read_pass); self.default_scene.renderer.set_pipeline(pipeline); @@ -408,8 +550,9 @@ impl WorldData { } } -mod js { - #[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#" +mod js_bindings { + use super::*; + #[wasm_bindgen(inline_js = r#" export function log_measurement(label, start, end) { window.performance.measure(label, { "start": start, "end": end }) } @@ -424,7 +567,7 @@ fn log_measurement(interval: &profiler::Interval) { let label = interval.label().to_owned(); let start = interval.start(); let end = interval.end(); - js::log_measurement(label, start, end); + js_bindings::log_measurement(label, start, end); } diff --git a/lib/rust/ensogl/core/src/gui/component.rs b/lib/rust/ensogl/core/src/gui/component.rs index b40c51c464..6e2e0f94c0 100644 --- a/lib/rust/ensogl/core/src/gui/component.rs +++ b/lib/rust/ensogl/core/src/gui/component.rs @@ -12,6 +12,7 @@ use crate::display::scene::Scene; use crate::display::shape::primitive::system::Shape; use crate::display::shape::primitive::system::ShapeInstance; use crate::display::symbol; +use crate::display::world; use crate::frp; @@ -23,6 +24,32 @@ pub use crate::display::scene::PointerTarget; +// ==================== +// === AnyShapeView === +// ==================== + +/// Generalization for any shape view. Allows storing different user-defined shapes in a single +/// collection. +pub trait AnyShapeView { + /// Get the shape's shader code in GLSL 330 format. The shader parameters will not be bound to + /// any particular mesh and thus this code can be used for optimization purposes only. + fn abstract_shader_code_in_glsl_310(&self) -> crate::system::gpu::shader::Code; + /// The shape definition path (file:line:column). + fn definition_path(&self) -> &'static str; +} + +impl AnyShapeView for ShapeView { + fn abstract_shader_code_in_glsl_310(&self) -> crate::system::gpu::shader::Code { + self.sprite.borrow().symbol.shader().abstract_shader_code_in_glsl_310() + } + + fn definition_path(&self) -> &'static str { + S::definition_path() + } +} + + + // ================= // === ShapeView === // ================= @@ -113,8 +140,7 @@ impl Drop for ShapeViewModel { impl ShapeViewModel { /// Constructor. pub fn new_with_data(data: S::ShapeData) -> Self { - let scene = scene(); - let (shape, _) = scene.layers.DETACHED.instantiate(&scene, &data); + let (shape, _) = world::with_context(|t| t.layers.DETACHED.instantiate(&data)); let events = PointerTarget::new(); let pointer_targets = default(); let data = RefCell::new(data); @@ -138,7 +164,7 @@ impl ShapeViewModel { } else { // Bug in clippy: https://github.com/rust-lang/rust-clippy/issues/9763 #[allow(clippy::explicit_auto_deref)] - let (shape, _) = scene.layers.DETACHED.instantiate(scene, &*self.data.borrow()); + let (shape, _) = scene.layers.DETACHED.instantiate(&*self.data.borrow()); self.shape.swap(&shape); } } @@ -160,7 +186,7 @@ impl ShapeViewModel { // Clippy error: https://github.com/rust-lang/rust-clippy/issues/9763 #[allow(clippy::explicit_auto_deref)] fn add_to_scene_layer(&self, scene: &Scene, layer: &scene::Layer) { - let (shape, instance) = layer.instantiate(scene, &*self.data.borrow()); + let (shape, instance) = layer.instantiate(&*self.data.borrow()); scene.pointer_target_registry.insert(instance.global_instance_id, self.events.clone_ref()); self.pointer_targets.borrow_mut().push(instance.global_instance_id); self.shape.swap(&shape); diff --git a/lib/rust/ensogl/core/src/system/gpu/context/native.rs b/lib/rust/ensogl/core/src/system/gpu/context/native.rs index 0d443ff9c0..d9cb1b680e 100644 --- a/lib/rust/ensogl/core/src/system/gpu/context/native.rs +++ b/lib/rust/ensogl/core/src/system/gpu/context/native.rs @@ -124,9 +124,9 @@ impl BlockingGetErrorLog for WebGl2RenderingContext { let error_loc_pfx = "ERROR: 0:"; let preview_code = if let Some(msg) = message.strip_prefix(error_loc_pfx) { let line_num: String = msg.chars().take_while(|c| c.is_ascii_digit()).collect(); - let line_num = line_num.parse::().unwrap() - 1; + let line_num = line_num.parse::().unwrap().saturating_sub(1); let preview_radius = 5; - let preview_line_start = std::cmp::max(0, line_num - preview_radius); + let preview_line_start = std::cmp::max(0, line_num.saturating_sub(preview_radius)); let preview_line_end = std::cmp::min(lines_num, line_num + preview_radius); let preview = lines_with_num[preview_line_start..preview_line_end].join("\n"); format!("...\n{}\n...", preview) diff --git a/lib/rust/ensogl/core/src/system/gpu/shader/glsl.rs b/lib/rust/ensogl/core/src/system/gpu/shader/glsl.rs index 721581ae52..d8c6e6257a 100644 --- a/lib/rust/ensogl/core/src/system/gpu/shader/glsl.rs +++ b/lib/rust/ensogl/core/src/system/gpu/shader/glsl.rs @@ -16,6 +16,35 @@ use nalgebra::OMatrix; +// =============== +// === Version === +// =============== + +/// Version of the GLSL to be used. Please note that WebGL 2.0 supports only GLSL 3.00 ES, however, +/// GLSL optimizers require GLSL 3.10+. Therefore, we need to generate GLSL 3.10+ code for the +/// optimizer and GLSL 3.00 ES for WebGL. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub enum Version { + #[default] + V300 = 300, + V310 = 310, +} + +impl Version { + /// The GLSL code header. + pub fn code(self) -> String { + format!("#version {} es", self as usize) + } + + /// Check whether the GLSL version requires explicit layout qualifiers on all shader's + /// parameters. + pub fn requires_layout(&self) -> bool { + *self > Version::V300 + } +} + + + // ================================================================================================= // === Glsl ======================================================================================== // ================================================================================================= @@ -308,7 +337,7 @@ impl HasCodeRepr for RawCode { // ================== /// Variable or type identifier. -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Deref, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct Identifier(pub String); impl HasCodeRepr for Identifier { @@ -335,6 +364,12 @@ impl From<&str> for Identifier { } } +impl From<&Identifier> for String { + fn from(t: &Identifier) -> Self { + t.0.clone() + } +} + // ============= @@ -487,7 +522,6 @@ impl HasCodeRepr for PrecisionDecl { // === AST Elements ================================================================================ // ================================================================================================= - // ============ // === Type === // ============ @@ -499,6 +533,13 @@ pub struct Type { pub array: Option, } +impl Type { + /// The layout size of this type. + pub fn layout_size(&self) -> usize { + self.prim.layout_size() * self.array.unwrap_or(1) + } +} + impl From for Type { fn from(prim: PrimType) -> Self { let array = None; @@ -526,105 +567,87 @@ impl Display for Type { // === PrimType === // ================ -/// Any non-array GLSL type. -#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum PrimType { - Float, - Int, - Void, - Bool, - Mat2, - Mat3, - Mat4, - Mat2x2, - Mat2x3, - Mat2x4, - Mat3x2, - Mat3x3, - Mat3x4, - Mat4x2, - Mat4x3, - Mat4x4, - Vec2, - Vec3, - Vec4, - IVec2, - IVec3, - IVec4, - BVec2, - BVec3, - BVec4, - UInt, - UVec2, - UVec3, - UVec4, - Sampler2d, - Sampler3d, - SamplerCube, - Sampler2dShadow, - SamplerCubeShadow, - Sampler2dArray, - Sampler2dArrayShadow, - ISampler2d, - ISampler3d, - ISamplerCube, - ISampler2dArray, - USampler2d, - USampler3d, - USamplerCube, - USampler2dArray, - Struct(Identifier), +macro_rules! define_prim_type { + ($($variant:ident { name = $name:literal, layout_size = $layout_size:literal }),* $(,)?) => { + /// Any non-array GLSL type. + #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] + pub enum PrimType { + $($variant,)* + Struct(Identifier), + } + + impl PrimType { + /// The GLSL name of this type. + pub fn glsl_name(&self) -> &str { + match self { + $(Self::$variant => $name,)* + Self::Struct(ident) => &ident, + } + } + + /// The layout size of this type. + pub fn layout_size(&self) -> usize { + match self { + $(Self::$variant => $layout_size,)* + Self::Struct(_) => 0, + } + } + } + }; +} + +// If `layout_size` is `0`, then this type can't be used as attribute, and thus, it does not +// contribute to the attribute layout. +define_prim_type! { + Float { name = "float", layout_size = 1 }, + Int { name = "int", layout_size = 1 }, + Void { name = "void", layout_size = 0 }, + Bool { name = "bool", layout_size = 1 }, + Mat2 { name = "mat2", layout_size = 2 }, + Mat3 { name = "mat3", layout_size = 3 }, + Mat4 { name = "mat4", layout_size = 4 }, + Mat2x2 { name = "mat2x2", layout_size = 2 }, + Mat2x3 { name = "mat2x3", layout_size = 2 }, + Mat2x4 { name = "mat2x4", layout_size = 2 }, + Mat3x2 { name = "mat3x2", layout_size = 3 }, + Mat3x3 { name = "mat3x3", layout_size = 3 }, + Mat3x4 { name = "mat3x4", layout_size = 3 }, + Mat4x2 { name = "mat4x2", layout_size = 4 }, + Mat4x3 { name = "mat4x3", layout_size = 4 }, + Mat4x4 { name = "mat4x4", layout_size = 4 }, + Vec2 { name = "vec2", layout_size = 1 }, + Vec3 { name = "vec3", layout_size = 1 }, + Vec4 { name = "vec4", layout_size = 1 }, + IVec2 { name = "ivec2", layout_size = 1 }, + IVec3 { name = "ivec3", layout_size = 1 }, + IVec4 { name = "ivec4", layout_size = 1 }, + BVec2 { name = "bvec2", layout_size = 1 }, + BVec3 { name = "bvec3", layout_size = 1 }, + BVec4 { name = "bvec4", layout_size = 1 }, + UInt { name = "int", layout_size = 1 }, + UVec2 { name = "uvec2", layout_size = 1 }, + UVec3 { name = "uvec3", layout_size = 1 }, + UVec4 { name = "uvec4", layout_size = 1 }, + Sampler2d { name = "sampler2D", layout_size = 0 }, + Sampler3d { name = "sampler3D", layout_size = 0 }, + SamplerCube { name = "samplerCube", layout_size = 0 }, + Sampler2dShadow { name = "sampler2DShadow", layout_size = 0 }, + SamplerCubeShadow { name = "samplerCubeShadow", layout_size = 0 }, + Sampler2dArray { name = "sampler2DArray", layout_size = 0 }, + Sampler2dArrayShadow { name = "sampler2DArrayShadow", layout_size = 0 }, + ISampler2d { name = "isampler2D", layout_size = 0 }, + ISampler3d { name = "isampler3D", layout_size = 0 }, + ISamplerCube { name = "isamplerCube", layout_size = 0 }, + ISampler2dArray { name = "isampler2DArray", layout_size = 0 }, + USampler2d { name = "usampler2D", layout_size = 0 }, + USampler3d { name = "usampler3D", layout_size = 0 }, + USamplerCube { name = "usamplerCube", layout_size = 0 }, + USampler2dArray { name = "usampler2DArray", layout_size = 0 }, } impl HasCodeRepr for PrimType { fn build(&self, builder: &mut CodeBuilder) { - match self { - Self::Float => builder.add("float"), - Self::Int => builder.add("int"), - Self::Void => builder.add("void"), - Self::Bool => builder.add("bool"), - Self::Mat2 => builder.add("mat2"), - Self::Mat3 => builder.add("mat3"), - Self::Mat4 => builder.add("mat4"), - Self::Mat2x2 => builder.add("mat2x2"), - Self::Mat2x3 => builder.add("mat2x3"), - Self::Mat2x4 => builder.add("mat2x4"), - Self::Mat3x2 => builder.add("mat3x2"), - Self::Mat3x3 => builder.add("mat3x3"), - Self::Mat3x4 => builder.add("mat3x4"), - Self::Mat4x2 => builder.add("mat4x2"), - Self::Mat4x3 => builder.add("mat4x3"), - Self::Mat4x4 => builder.add("mat4x4"), - Self::Vec2 => builder.add("vec2"), - Self::Vec3 => builder.add("vec3"), - Self::Vec4 => builder.add("vec4"), - Self::IVec2 => builder.add("ivec2"), - Self::IVec3 => builder.add("ivec3"), - Self::IVec4 => builder.add("ivec4"), - Self::BVec2 => builder.add("bvec2"), - Self::BVec3 => builder.add("bvec3"), - Self::BVec4 => builder.add("bvec4"), - Self::UInt => builder.add("int"), - Self::UVec2 => builder.add("uvec2"), - Self::UVec3 => builder.add("uvec3"), - Self::UVec4 => builder.add("uvec4"), - Self::Sampler2d => builder.add("sampler2D"), - Self::Sampler3d => builder.add("sampler3D"), - Self::SamplerCube => builder.add("samplerCube"), - Self::Sampler2dShadow => builder.add("sampler2DShadow"), - Self::SamplerCubeShadow => builder.add("samplerCubeShadow"), - Self::Sampler2dArray => builder.add("sampler2DArray"), - Self::Sampler2dArrayShadow => builder.add("sampler2DArrayShadow"), - Self::ISampler2d => builder.add("isampler2D"), - Self::ISampler3d => builder.add("isampler3D"), - Self::ISamplerCube => builder.add("isamplerCube"), - Self::ISampler2dArray => builder.add("isampler2DArray"), - Self::USampler2d => builder.add("usampler2D"), - Self::USampler3d => builder.add("usampler3D"), - Self::USamplerCube => builder.add("usamplerCube"), - Self::USampler2dArray => builder.add("usampler2DArray"), - Self::Struct(ident) => builder.add(ident), - }; + builder.add(self.glsl_name()); } } @@ -811,20 +834,22 @@ impl From<&Precision> for Precision { /// Translation unit definition. It represents the whole GLSL file. #[derive(Clone, Debug)] pub struct Module { + pub version: Version, pub prec_decls: Vec, pub global_vars: Vec, pub statements: Vec, pub main: Function, } -impl Default for Module { - fn default() -> Self { +impl Module { + /// Constructor. + pub fn new(version: Version) -> Self { let prec_decls = default(); let global_vars = default(); let statements = default(); let main = Function { typ: PrimType::Void.into(), ident: "main".into(), body: default() }; - Self { prec_decls, global_vars, statements, main } + Self { version, prec_decls, global_vars, statements, main } } } @@ -858,7 +883,7 @@ impl AddMut for Module { impl HasCodeRepr for Module { fn build(&self, builder: &mut CodeBuilder) { - builder.add("#version 300 es"); + builder.add(&self.version.code()); builder.newline(); builder.newline(); diff --git a/lib/rust/ensogl/core/src/system/js.rs b/lib/rust/ensogl/core/src/system/js.rs index 96fb70fd6b..ed235a5c81 100644 --- a/lib/rust/ensogl/core/src/system/js.rs +++ b/lib/rust/ensogl/core/src/system/js.rs @@ -5,6 +5,9 @@ // === Export === // ============== +pub mod app; pub mod typed_array; +pub use app::app; +pub use app::app_or_panic; pub use typed_array::*; diff --git a/lib/rust/ensogl/core/src/system/js/app.rs b/lib/rust/ensogl/core/src/system/js/app.rs new file mode 100644 index 0000000000..62d231ee08 --- /dev/null +++ b/lib/rust/ensogl/core/src/system/js/app.rs @@ -0,0 +1,116 @@ +//! Binding to the EnsoGL TypeScript App class. This module does not provide docs for the app +//! methods. You can find them in the TypeScript source code. + +// === Non-Standard Linter Configuration === +#![allow(missing_docs)] + +use crate::prelude::*; +use crate::system::web::traits::*; +use crate::system::web::*; + + + +// =================== +// === JS Bindings === +// =================== + +#[cfg(target_arch = "wasm32")] +pub mod js_bindings { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + extern "C" { + pub type App; + pub type Config; + pub type Params; + pub type Param; + + /// Register in JS a closure to get non-precompiled shaders from Rust. + #[allow(unsafe_code)] + #[wasm_bindgen(method)] + #[wasm_bindgen(js_name = registerGetShadersRustFn)] + pub fn register_get_shaders_rust_fn(this: &App, closure: &Closure JsValue>); + + /// Register in JS a closure to set precompiled shaders in Rust. + #[allow(unsafe_code)] + #[wasm_bindgen(method)] + #[wasm_bindgen(js_name = registerSetShadersRustFn)] + pub fn register_set_shaders_rust_fn(this: &App, closure: &Closure); + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub mod js_bindings { + use super::*; + use enso_web::mock_data; + + mock_data! { App => JsValue } + mock_data! { Config => JsValue } + mock_data! { Params => JsValue } + mock_data! { Param => JsValue } + + impl App { + pub fn register_get_shaders_rust_fn(&self, _closure: &Closure JsValue>) {} + pub fn register_set_shaders_rust_fn(&self, _closure: &Closure) {} + } +} + +use js_bindings::*; + + + +// =========== +// === App === +// =========== + +impl App { + pub fn config(&self) -> Config { + Reflect::get(self, &"config".into()).unwrap().unchecked_into::() + } +} + +impl Config { + pub fn params(&self) -> Params { + Reflect::get(self, &"params".into()).unwrap().unchecked_into() + } +} + +impl Params { + pub fn get(&self, name: &str) -> Result { + Reflect::get(self, &name.into()).map(|t| t.unchecked_into()) + } + + pub fn to_vec(&self) -> Vec { + let obj = (*self).clone().unchecked_into::(); + let keys = Object::keys_vec(&obj); + keys.iter().map(|key| self.get(key).unwrap()).collect() + } + + pub fn to_hash_map(&self) -> HashMap { + let obj = (*self).clone().unchecked_into::(); + let keys = Object::keys_vec(&obj); + keys.iter().map(|key| (key.clone(), self.get(key).unwrap())).collect() + } +} + +impl Param { + pub fn value(&self) -> Option { + let val = Reflect::get(self, &"value".into()).unwrap(); + if val.is_null() || val.is_undefined() { + None + } else { + Some(val.print_to_string()) + } + } +} + +pub fn app() -> Result { + Reflect::get_nested_object(&window, &["ensoglApp"]).map(|t| t.unchecked_into()) +} + +pub fn app_or_panic() -> App { + match app() { + Ok(app) => app, + Err(_) => panic!("Failed to get JavaScript EnsoGL app."), + } +} diff --git a/lib/rust/ensogl/example/animation/src/lib.rs b/lib/rust/ensogl/example/animation/src/lib.rs index 9218d7e24a..39ac60123e 100644 --- a/lib/rust/ensogl/example/animation/src/lib.rs +++ b/lib/rust/ensogl/example/animation/src/lib.rs @@ -23,7 +23,6 @@ #![warn(unused_qualifications)] use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::application::Application; use ensogl_core::DEPRECATED_Animation; @@ -47,7 +46,7 @@ pub fn main() { enso_frp::extend! {network eval animation.value([](value) { - info!("Value {value}") + warn!("Value {value}") }); } diff --git a/lib/rust/ensogl/example/auto-layout/Cargo.toml b/lib/rust/ensogl/example/auto-layout/Cargo.toml index fc6bbf1326..16c90d25b8 100644 --- a/lib/rust/ensogl/example/auto-layout/Cargo.toml +++ b/lib/rust/ensogl/example/auto-layout/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] ensogl-core = { path = "../../core" } wasm-bindgen = { workspace = true } +ensogl-hardcoded-theme = { version = "0.1.0", path = "../../../ensogl/app/theme/hardcoded" } # Stop wasm-pack from running wasm-opt, because we run it from our build scripts in order to customize options. [package.metadata.wasm-pack.profile.release] diff --git a/lib/rust/ensogl/example/auto-layout/src/lib.rs b/lib/rust/ensogl/example/auto-layout/src/lib.rs index 2e22eb9b09..d1fe29d1df 100644 --- a/lib/rust/ensogl/example/auto-layout/src/lib.rs +++ b/lib/rust/ensogl/example/auto-layout/src/lib.rs @@ -9,7 +9,6 @@ use ensogl_core::display::shape::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::data::color; use ensogl_core::display; diff --git a/lib/rust/ensogl/example/complex-shape-system/src/lib.rs b/lib/rust/ensogl/example/complex-shape-system/src/lib.rs index 8ec112ec0e..766f30988a 100644 --- a/lib/rust/ensogl/example/complex-shape-system/src/lib.rs +++ b/lib/rust/ensogl/example/complex-shape-system/src/lib.rs @@ -9,7 +9,6 @@ use ensogl_core::display::shape::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::data::color; use ensogl_core::display::navigation::navigator::Navigator; @@ -25,8 +24,10 @@ use ensogl_core::system::web; // ============== mod shape { + // This scene uses the `shape_old` definition because it demonstrates dynamic theme changes + // which is currently not working with shader-precompilation. use super::*; - ensogl_core::shape! { + ensogl_core::shape_old! { (style:Style) { let base_color = style.get_color("base_color"); let circle1 = Circle(50.px()); @@ -42,7 +43,7 @@ mod shape { mod mask { use super::*; - ensogl_core::shape! { + ensogl_core::shape_old! { (style:Style) { let shape = Circle(60.px()); let shape = shape.fill(color::Rgb::new(1.0,0.0,0.0)); diff --git a/lib/rust/ensogl/example/custom-shape-system/src/lib.rs b/lib/rust/ensogl/example/custom-shape-system/src/lib.rs index 06f99cca2a..cb432b9bb5 100644 --- a/lib/rust/ensogl/example/custom-shape-system/src/lib.rs +++ b/lib/rust/ensogl/example/custom-shape-system/src/lib.rs @@ -9,7 +9,6 @@ use ensogl_core::display::shape::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use enso_frp as frp; use ensogl_core::display::navigation::navigator::Navigator; diff --git a/lib/rust/ensogl/example/dom-symbols/src/lib.rs b/lib/rust/ensogl/example/dom-symbols/src/lib.rs index 3ef4e95dec..19451c8dbe 100644 --- a/lib/rust/ensogl/example/dom-symbols/src/lib.rs +++ b/lib/rust/ensogl/example/dom-symbols/src/lib.rs @@ -22,7 +22,6 @@ use ensogl_core::display::world::*; use ensogl_core::prelude::*; use ensogl_core::system::web::traits::*; -use wasm_bindgen::prelude::*; use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::symbol::geometry::Sprite; @@ -59,8 +58,9 @@ fn update_shape(screen: Shape, sprites: &[Sprite], dom_symbols: &[DomSymbol]) { object.set_position(Vector3(x, 0.0, 0.0)); } for symbol in sprites { + warn!("setting sprite size: {:?}", Vector2(width, height * HEIGHT_FRACTION)); let size = Vector2::new(width, height * HEIGHT_FRACTION); - symbol.size.set(size); + symbol.set_size(size); symbol.update_y(|y| y - screen.height / 2.0 + size.y / 2.0 + VERTICAL_MARGIN); } for symbol in dom_symbols { @@ -94,6 +94,7 @@ pub fn main() { let fi = i as f32; if i % 2 == 0 { let sprite = sprite_system.new_instance(); + world.add_child(&sprite); sprites.push(sprite); } else { let div = web::document.create_div_or_panic(); diff --git a/lib/rust/ensogl/example/drop-down/src/lib.rs b/lib/rust/ensogl/example/drop-down/src/lib.rs index 9c30a6f141..93819618bd 100644 --- a/lib/rust/ensogl/example/drop-down/src/lib.rs +++ b/lib/rust/ensogl/example/drop-down/src/lib.rs @@ -8,14 +8,12 @@ #![allow(clippy::let_and_return)] use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::application::Application; use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::object::ObjectOps; use ensogl_drop_down::Dropdown; use ensogl_drop_down::DropdownValue; -use ensogl_hardcoded_theme as theme; use ensogl_text_msdf::run_once_initialized; @@ -42,10 +40,6 @@ pub fn main() { // ======================== fn init(app: &Application) { - theme::builtin::dark::register(app); - theme::builtin::light::register(app); - theme::builtin::light::enable(app); - let world = &app.display; let scene = &world.default_scene; let navigator = Navigator::new(scene, &scene.camera()); diff --git a/lib/rust/ensogl/example/drop-manager/src/lib.rs b/lib/rust/ensogl/example/drop-manager/src/lib.rs index a827fd84ac..c3a762a127 100644 --- a/lib/rust/ensogl/example/drop-manager/src/lib.rs +++ b/lib/rust/ensogl/example/drop-manager/src/lib.rs @@ -24,7 +24,6 @@ #![warn(unused_qualifications)] use enso_prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::display::world::World; use ensogl_core::frp::web; diff --git a/lib/rust/ensogl/example/easing-animator/src/lib.rs b/lib/rust/ensogl/example/easing-animator/src/lib.rs index 373915004d..d0aabf409f 100644 --- a/lib/rust/ensogl/example/easing-animator/src/lib.rs +++ b/lib/rust/ensogl/example/easing-animator/src/lib.rs @@ -33,7 +33,6 @@ use nalgebra::Vector2; use std::ops::Add; use std::ops::Mul; use std::rc::Rc; -use wasm_bindgen::prelude::wasm_bindgen; use web::CanvasRenderingContext2d; use web::HtmlCanvasElement; diff --git a/lib/rust/ensogl/example/focus-management/src/lib.rs b/lib/rust/ensogl/example/focus-management/src/lib.rs index 3ddef70b6b..6727e2f4db 100644 --- a/lib/rust/ensogl/example/focus-management/src/lib.rs +++ b/lib/rust/ensogl/example/focus-management/src/lib.rs @@ -9,7 +9,6 @@ use ensogl_core::display::shape::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::data::color; use ensogl_core::display::navigation::navigator::Navigator; diff --git a/lib/rust/ensogl/example/grid-view/src/lib.rs b/lib/rust/ensogl/example/grid-view/src/lib.rs index 17fc42cd1c..38aa8c52c4 100644 --- a/lib/rust/ensogl/example/grid-view/src/lib.rs +++ b/lib/rust/ensogl/example/grid-view/src/lib.rs @@ -23,7 +23,6 @@ #![warn(unused_qualifications)] use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use enso_frp as frp; use ensogl_core::application::Application; @@ -34,7 +33,6 @@ use ensogl_grid_view as grid_view; use ensogl_grid_view::Col; use ensogl_grid_view::Margins; use ensogl_grid_view::Row; -use ensogl_hardcoded_theme as theme; use ensogl_text_msdf::run_once_initialized; @@ -172,10 +170,6 @@ fn pair_to_vec2(pair: (f32, f32)) -> Vector2 { // ======================== fn init(app: &Application) { - theme::builtin::dark::register(app); - theme::builtin::light::register(app); - theme::builtin::light::enable(app); - let main_layer = &app.display.default_scene.layers.node_searcher; let grids_layer = main_layer.create_sublayer("grids"); let hover_layer = main_layer.create_sublayer("hover"); diff --git a/lib/rust/ensogl/example/list-view/src/lib.rs b/lib/rust/ensogl/example/list-view/src/lib.rs index 97dbf226a7..b73d89e110 100644 --- a/lib/rust/ensogl/example/list-view/src/lib.rs +++ b/lib/rust/ensogl/example/list-view/src/lib.rs @@ -23,12 +23,10 @@ #![warn(unused_qualifications)] use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use enso_text::index::Byte; use ensogl_core::application::Application; use ensogl_core::display::object::ObjectOps; -use ensogl_hardcoded_theme as theme; use ensogl_list_view as list_view; use ensogl_text_msdf::run_once_initialized; @@ -89,10 +87,6 @@ impl list_view::entry::ModelProvider fo // ======================== fn init(app: &Application) { - theme::builtin::dark::register(app); - theme::builtin::light::register(app); - theme::builtin::light::enable(app); - let list_view = app.new_view::>(); let provider = list_view::entry::AnyModelProvider::new(MockEntries::new(1000)); list_view.frp.resize(Vector2(100.0, 160.0)); diff --git a/lib/rust/ensogl/example/mouse-events/src/lib.rs b/lib/rust/ensogl/example/mouse-events/src/lib.rs index 7cb7a43896..aee8c93463 100644 --- a/lib/rust/ensogl/example/mouse-events/src/lib.rs +++ b/lib/rust/ensogl/example/mouse-events/src/lib.rs @@ -24,7 +24,6 @@ use ensogl_core::display::shape::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use enso_frp as frp; use ensogl_core::application; diff --git a/lib/rust/ensogl/example/scroll-area/src/lib.rs b/lib/rust/ensogl/example/scroll-area/src/lib.rs index 6e6db80dcd..18abe7ad83 100644 --- a/lib/rust/ensogl/example/scroll-area/src/lib.rs +++ b/lib/rust/ensogl/example/scroll-area/src/lib.rs @@ -24,12 +24,10 @@ use ensogl_core::display::shape::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::application::Application; use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::object::ObjectOps; -use ensogl_hardcoded_theme as theme; use ensogl_scroll_area::ScrollArea; use ensogl_text_msdf::run_once_initialized; @@ -96,10 +94,6 @@ mod background { // ======================== fn init(app: &Application) { - theme::builtin::dark::register(app); - theme::builtin::light::register(app); - theme::builtin::light::enable(app); - let scene = &app.display.default_scene; scene.camera().set_xy(Vector2(100.0, -100.0)); diff --git a/lib/rust/ensogl/example/slider/src/lib.rs b/lib/rust/ensogl/example/slider/src/lib.rs index 8484f3aff8..28e553308b 100644 --- a/lib/rust/ensogl/example/slider/src/lib.rs +++ b/lib/rust/ensogl/example/slider/src/lib.rs @@ -22,7 +22,6 @@ #![warn(unused_qualifications)] use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use enso_frp as frp; use ensogl_core::application::shortcut; @@ -30,7 +29,6 @@ use ensogl_core::application::Application; use ensogl_core::application::View; use ensogl_core::data::color; use ensogl_core::display; -use ensogl_hardcoded_theme as theme; use ensogl_slider as slider; use ensogl_text_msdf::run_once_initialized; @@ -327,10 +325,6 @@ pub fn main() { /// Initialize a `SliderCollection` and do not drop it. fn init(app: &Application) { - theme::builtin::dark::register(app); - theme::builtin::light::register(app); - theme::builtin::light::enable(app); - let slider_collection = app.new_view::(); app.display.add_child(&slider_collection); Leak::new(slider_collection); diff --git a/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs b/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs index fc1a9cc1a8..9c6292c42f 100644 --- a/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs +++ b/lib/rust/ensogl/example/sprite-system-benchmark/src/lib.rs @@ -21,7 +21,6 @@ use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::animation; use ensogl_core::display::camera::Camera2d; @@ -43,7 +42,7 @@ pub fn main() { let sprite_system = SpriteSystem::new(); let sprite1 = sprite_system.new_instance(); - sprite1.size.set(Vector2::new(10.0, 10.0)); + sprite1.set_size(Vector2::new(10.0, 10.0)); sprite1.set_position(Vector3::new(5.0, 5.0, 0.0)); scene.add_child(&sprite_system); @@ -52,7 +51,7 @@ pub fn main() { let count = 100; for _ in 0..count { let sprite = sprite_system.new_instance(); - sprite.size.set(Vector2::new(1.0, 1.0)); + sprite.set_size(Vector2::new(1.0, 1.0)); sprites.push(sprite); } @@ -106,7 +105,7 @@ pub fn on_frame( if *iter < cycle_duration { for _ in 0..sprite_diff_per_cycle { let sprite = sprite_system.new_instance(); - sprite.size.set(Vector2::new(1.0, 1.0)); + sprite.set_size(Vector2::new(1.0, 1.0)); sprites.push(sprite); } } else if *iter < pause_duration + cycle_duration { diff --git a/lib/rust/ensogl/example/sprite-system/src/lib.rs b/lib/rust/ensogl/example/sprite-system/src/lib.rs index ba178e18ca..e985e95646 100644 --- a/lib/rust/ensogl/example/sprite-system/src/lib.rs +++ b/lib/rust/ensogl/example/sprite-system/src/lib.rs @@ -21,7 +21,6 @@ use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use wasm_bindgen::prelude::*; use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::symbol::geometry::SpriteSystem; @@ -37,8 +36,8 @@ pub fn main() { let sprite2 = sprite_system.new_instance(); let sprite1 = sprite_system.new_instance(); - sprite1.size.set(Vector2::new(15.0, 15.0)); - sprite2.size.set(Vector2::new(15.0, 15.0)); + sprite1.set_size(Vector2::new(15.0, 15.0)); + sprite2.set_size(Vector2::new(15.0, 15.0)); world.default_scene.add_child(&sprite_system); world.keep_alive_forever(); diff --git a/lib/rust/ensogl/example/text-area/src/lib.rs b/lib/rust/ensogl/example/text-area/src/lib.rs index f4b46b629f..d243c7222d 100644 --- a/lib/rust/ensogl/example/text-area/src/lib.rs +++ b/lib/rust/ensogl/example/text-area/src/lib.rs @@ -40,7 +40,6 @@ use ensogl_text::buffer; use ensogl_text::formatting; use ensogl_text::Text; use ensogl_text_msdf::run_once_initialized; -use wasm_bindgen::prelude::wasm_bindgen; diff --git a/lib/rust/ensogl/pack/Cargo.lock b/lib/rust/ensogl/pack/Cargo.lock new file mode 100644 index 0000000000..94f55fd70f --- /dev/null +++ b/lib/rust/ensogl/pack/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ensogl-pack" +version = "0.1.0" diff --git a/lib/rust/ensogl/pack/Cargo.toml b/lib/rust/ensogl/pack/Cargo.toml new file mode 100644 index 0000000000..7d92aa3881 --- /dev/null +++ b/lib/rust/ensogl/pack/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ensogl-pack" +version = "0.1.0" +authors = ["Enso Team "] +edition = "2021" + +[lib] +crate-type = ["rlib"] + +[dependencies] +ide-ci = { path = "../../../../build/ci_utils" } +manifest-dir-macros = "0.1.16" +regex = { workspace = true } +tempfile = "3" +tokio = { workspace = true } +fs_extra = "1.2.0" +walkdir = "2" +enso-prelude = { path = "../../prelude" } diff --git a/lib/rust/ensogl/pack/js/.eslintrc.cjs b/lib/rust/ensogl/pack/js/.eslintrc.cjs new file mode 100644 index 0000000000..93231e399d --- /dev/null +++ b/lib/rust/ensogl/pack/js/.eslintrc.cjs @@ -0,0 +1,73 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/strict', + ], + parser: '@typescript-eslint/parser', + plugins: ['jsdoc', '@typescript-eslint'], + root: true, + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/naming-convention': 'error', + '@typescript-eslint/no-unnecessary-condition': 'error', + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowAny: true, + allowBoolean: true, + }, + ], + 'jsdoc/check-access': 'warn', + 'jsdoc/check-alignment': 'warn', + 'jsdoc/check-indentation': 'warn', + 'jsdoc/check-line-alignment': 'warn', + 'jsdoc/check-param-names': 'warn', + 'jsdoc/check-property-names': 'warn', + 'jsdoc/check-syntax': 'warn', + 'jsdoc/check-tag-names': 'warn', + 'jsdoc/check-types': 'warn', + 'jsdoc/check-values': 'warn', + 'jsdoc/empty-tags': 'warn', + 'jsdoc/implements-on-classes': 'warn', + 'jsdoc/no-bad-blocks': 'warn', + 'jsdoc/no-defaults': 'warn', + 'jsdoc/no-multi-asterisks': 'warn', + 'jsdoc/no-types': 'warn', + 'jsdoc/no-undefined-types': 'warn', + 'jsdoc/require-asterisk-prefix': 'warn', + 'jsdoc/require-description': 'warn', + // Disabled because of a bug: https://github.com/gajus/eslint-plugin-jsdoc/issues/942 + // 'jsdoc/require-description-complete-sentence': 'warn, + 'jsdoc/require-file-overview': 'warn', + 'jsdoc/require-hyphen-before-param-description': 'warn', + 'jsdoc/require-jsdoc': 'warn', + 'jsdoc/require-param-description': 'warn', + 'jsdoc/require-param-name': 'warn', + 'jsdoc/require-param-type': 'warn', + 'jsdoc/require-property': 'warn', + 'jsdoc/require-property-description': 'warn', + 'jsdoc/require-property-name': 'warn', + 'jsdoc/require-property-type': 'warn', + 'jsdoc/require-returns-check': 'warn', + 'jsdoc/require-returns-description': 'warn', + 'jsdoc/require-throws': 'warn', + 'jsdoc/require-yields': 'warn', + 'jsdoc/require-yields-check': 'warn', + 'jsdoc/tag-lines': 'warn', + 'jsdoc/valid-types': 'warn', + }, + overrides: [ + { + files: ['*.ts', '*.tsx'], + parserOptions: { + project: ['./tsconfig.json'], + }, + }, + ], +} diff --git a/lib/rust/ensogl/pack/js/package.json b/lib/rust/ensogl/pack/js/package.json new file mode 100644 index 0000000000..f27605609e --- /dev/null +++ b/lib/rust/ensogl/pack/js/package.json @@ -0,0 +1,40 @@ +{ + "name": "ensogl-runner", + "version": "1.0.0", + "type": "module", + "author": { + "name": "Enso Team", + "email": "contact@enso.org" + }, + "homepage": "https://github.com/enso-org/enso", + "repository": { + "type": "git", + "url": "git@github.com:enso-org/enso.git" + }, + "@comment scripts": { + "build": "We are using tsup to be able to generate the bundled .d.ts file. It is the only solution that works. See the following link for reference: https://github.com/Microsoft/TypeScript/issues/4433" + }, + "scripts": { + "typecheck": "npx tsc --noEmit", + "build": "npx --yes tsup src/runner/index.ts --format=cjs --dts --sourcemap", + "build-shader-extractor": "npx --yes tsup --format=cjs --target=esnext src/shader-extractor/shader-extractor.ts --dts --sourcemap", + "build-runtime-libs": "npx --yes esbuild --bundle --platform=node --format=cjs src/runtime-libs/runtime-libs.ts", + "lint": "npx --yes eslint src" + }, + "bugs": { + "url": "https://github.com/enso-org/enso/issues" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.47.1", + "@typescript-eslint/parser": "^5.47.1", + "esbuild": "^0.16.12", + "eslint": "^8.30.0", + "eslint-plugin-jsdoc": "^39.6.4", + "ts-node": "^10.9.1", + "tsup": "^6.5.0", + "typescript": "^4.9.4" + }, + "dependencies": { + "spectorjs": "^0.9.27" + } +} diff --git a/lib/rust/ensogl/pack/js/src/runner/animation.ts b/lib/rust/ensogl/pack/js/src/runner/animation.ts new file mode 100644 index 0000000000..474fe7e85c --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/animation.ts @@ -0,0 +1,68 @@ +/** @file Easing is a method of distorting time to control apparent motion in animation. This module + * defines a set of easing functions. To learn more about them and see graphs of how they behave, + * see: https://github.com/d3/d3-ease. */ + +// ================= +// === Constants === +// ================= + +/** Two power minus ten times t scaled to [0,1]. */ +export function tpmt(x: number) { + return (Math.pow(2, -10 * x) - 0.0009765625) * 1.0009775171065494 +} + +const tau = 2 * Math.PI, + amplitude = 1, + period = 0.3 + +// ================= +// === Animation === +// ================= + +/** Symmetric quadratic easing. + * To learn more, see: https://github.com/d3/d3-ease#easeQuadOut. */ +export function easeInOutQuad(t: number): number { + return t < 0.5 ? 2 * t * t : 1 - ((-2 * t + 2) * (-2 * t + 2)) / 2 +} + +/** Reverse elastic easing; equivalent to `1 - elasticIn(1 - t)`. + * To learn more, see: https://github.com/d3/d3-ease#easeElasticOut. */ +export const elasticOut = (function custom(a: number, p: number) { + const s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau) + + const elasticOut = (t: number) => { + return 1 - a * tpmt((t = +t)) * Math.sin((t + s) / p) + } + + elasticOut.amplitude = function (a: number) { + return custom(a, p * tau) + } + elasticOut.period = function (p: number) { + return custom(a, p) + } + + return elasticOut +})(amplitude, period) + +/** Symmetric elastic easing; scales elasticIn for t in [0, 0.5] and elasticOut for t in [0.5, 1]. + * To learn more, see: https://github.com/d3/d3-ease#easeElasticInOut. */ +export const elasticInOut = (function custom(a: number, p: number) { + const s = Math.asin(1 / (a = Math.max(1, a))) * (p /= tau) + + const elasticInOut = (t: number) => { + return ( + ((t = t * 2 - 1) < 0 + ? a * tpmt(-t) * Math.sin((s - t) / p) + : 2 - a * tpmt(t) * Math.sin((s + t) / p)) / 2 + ) + } + + elasticInOut.amplitude = function (a: number) { + return custom(a, p * tau) + } + elasticInOut.period = function (p: number) { + return custom(a, p) + } + + return elasticInOut +})(amplitude, period) diff --git a/lib/rust/ensogl/pack/js/src/runner/config.ts b/lib/rust/ensogl/pack/js/src/runner/config.ts new file mode 100644 index 0000000000..76b6a11c30 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/config.ts @@ -0,0 +1,216 @@ +/** @file Configuration options for the application. */ + +import { logger } from 'runner/log' + +export const DEFAULT_ENTRY_POINT = 'ide' + +// ============= +// === Utils === +// ============= + +/** Parses the provided value as boolean. If it was a boolean value, it is left intact. If it was + * a string 'true', 'false', '1', or '0', it is converted to a boolean value. Otherwise, null is + * returned. */ +// prettier-ignore +function parseBoolean(value: any): boolean | null { + switch(value) { + case true: return true + case false: return false + case 'true': return true + case 'false': return false + case 'enabled': return true + case 'disabled': return false + case 'yes': return true + case 'no': return false + case '1': return true + case '0': return false + default: return null + } +} + +// ============= +// === Param === +// ============= + +/** A valid parameter value. */ +export type ParamValue = string | boolean | number | (string | null) + +/** Configuration parameter. */ +export class Param { + default: T + value: T + description: string + setByUser = false + constructor(value: T, description: string) { + this.default = value + this.value = value + this.description = description + } +} + +// ============== +// === Params === +// ============== + +export type ExternalConfig = Record + +/** Application default configuration. Users of this library can extend it with their own + * options. */ +export class Params { + [key: string]: Param + + pkgWasmUrl = new Param( + 'pkg.wasm', + 'The URL of the WASM pkg file generated by ensogl-pack.' + ) + pkgJsUrl = new Param('pkg.js', 'The URL of the JS pkg file generated by ensogl-pack.') + shadersUrl = new Param('shaders', 'The URL of the pre-compiled shaders directory.') + entry = new Param( + DEFAULT_ENTRY_POINT, + 'The application entry point. Use `entry=_` to list available entry points.' + ) + theme = new Param('default', 'The EnsoGL theme to be used.') + useLoader = new Param( + true, + 'Controls whether the visual loader should be visible on the screen when downloading and ' + + 'compiling WASM sources. By default, the loader is used only if the `entry` is set ' + + `to ${DEFAULT_ENTRY_POINT}.` + ) + loaderDownloadToInitRatio = new Param( + 1.0, + 'The (time needed for WASM download) / (total time including WASM download and WASM app ' + + 'initialization). In case of small WASM apps, this can be set to 1.0. In case of ' + + 'bigger WASM apps, it is desired to show the progress bar growing up to e.g. 70% and ' + + 'leaving the last 30% for WASM app init.' + ) + debug = new Param( + false, + 'Controls whether the application should be run in the debug mode. In this mode all logs ' + + 'are printed to the console. Otherwise, the logs are hidden unless explicitly shown ' + + 'by calling `showLogs`. Moreover, EnsoGL extensions are loaded in the debug mode ' + + 'which may cause additional logs to be printed.' + ) + maxBeforeMainEntryPointsTimeMs = new Param( + 300, + 'The maximum time in milliseconds a before main entry point is allowed to run. After ' + + 'this time, an error will be printed, but the execution will continue.' + ) + enableSpector = new Param( + false, + 'Enables SpectorJS. This is a temporary flag to test Spector. It will be removed after ' + + 'all Spector integration issues are resolved. See: ' + + 'https://github.com/BabylonJS/Spector.js/issues/252.' + ) +} + +// ============== +// === Config === +// ============== + +/** The configuration of the EnsoGL application. The options can be overriden by the user. The + * implementation automatically casts the values to the correct types. For example, if an option + * override for type boolean was provided as `'true'`, it will be parsed automatically. Moreover, + * it is possible to extend the provided option list with custom options. See the `extend` method + * to learn more. */ +export class Config { + params = new Params() + + constructor(config?: ExternalConfig) { + this.extend(config) + } + + /** Extend the configuration with user provided options. */ + extend(config?: ExternalConfig) { + if (config != null) { + Object.assign(this.params, config) + } + } + + /** Resolve the configuration from the provided record list. + * @returns list of unrecognized parameters. */ + resolve(overrides: (Record | undefined)[]): null | string[] { + const allOverrides = {} + for (const override of overrides) { + if (override != null) { + Object.assign(allOverrides, override) + } + } + const unrecognizedParams = this.resolveFromObject(allOverrides) + this.finalize() + return unrecognizedParams + } + + /** Resolve the configuration from the provided record. + * @returns list of unrecognized parameters. */ + resolveFromObject(other: Record): null | string[] { + const paramsToBeAssigned = new Set(Object.keys(other)) + const invalidParams = new Set() + for (const key of Object.keys(this.params)) { + paramsToBeAssigned.delete(key) + const otherVal: unknown = other[key] + const param = this.params[key] + if (param == null) { + invalidParams.add(key) + } else { + const selfVal = param.value + if (otherVal != null) { + if (typeof selfVal === 'boolean') { + const newVal = parseBoolean(otherVal) + if (newVal == null) { + this.printValueUpdateError(key, selfVal, otherVal) + } else { + param.value = newVal + param.setByUser = true + } + } else if (typeof selfVal == 'number') { + const newVal = Number(otherVal) + if (isNaN(newVal)) { + this.printValueUpdateError(key, selfVal, otherVal) + } else { + param.value = newVal + param.setByUser = true + } + } else { + param.value = String(otherVal) + param.setByUser = true + } + } + } + } + if (paramsToBeAssigned.size > 0 || invalidParams.size > 0) { + return [...paramsToBeAssigned, ...invalidParams] + } else { + return null + } + } + + /** Finalize the configuration. Set some default options based on the provided values. */ + finalize() { + if (!this.params.useLoader.setByUser && this.params.entry.value !== DEFAULT_ENTRY_POINT) { + this.params.useLoader.value = false + } + } + + printValueUpdateError(key: string, selfVal: any, otherVal: any) { + console.error( + `The provided value for Config.${key} is invalid. Expected boolean, got '${otherVal}'. \ + Using the default value '${selfVal}' instead.` + ) + } + + strigifiedKeyValueMap(): Record { + const map: Record = {} + for (const [key, param] of Object.entries(this.params)) { + if (param.value) { + map[key] = param.value.toString() + } else { + map[key] = param.value + } + } + return map + } + + print() { + logger.log(`Resolved config:`, this.strigifiedKeyValueMap()) + } +} diff --git a/lib/rust/ensogl/pack/js/src/runner/data/array.ts b/lib/rust/ensogl/pack/js/src/runner/data/array.ts new file mode 100644 index 0000000000..b4cc229702 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/data/array.ts @@ -0,0 +1,24 @@ +/** @file Array extensions. */ + +/** Converts an array into tuples. Return null if the provided array element count is not even. */ +export function arrayIntoTuples(arr: T[]): [T, T][] | null { + if (arr.length % 2 !== 0) { + return null + } else { + const tuples: [T, T][] = [] + for (let i = 0; i < arr.length; i += 2) { + const elem1 = arr[i] + const elem2 = arr[i + 1] + if (elem1 != null && elem2 != null) { + tuples.push([elem1, elem2]) + } + } + return tuples + } +} + +/** Zips two arrays and returns an array of tuples. */ +export function zip(arr1: T[], arr2: S[]): [T, S][] { + // @ts-expect-error + return [...arr1].map((_, c) => [arr1, arr2].map(row => row[c])) +} diff --git a/lib/rust/ensogl/pack/js/src/runner/debug.ts b/lib/rust/ensogl/pack/js/src/runner/debug.ts new file mode 100644 index 0000000000..7a3d7c9748 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/debug.ts @@ -0,0 +1,4 @@ +/** @file Debug information and utilities. */ + +export * from './debug/package-info' +export * from './debug/help-screen' diff --git a/lib/rust/ensogl/pack/js/src/runner/debug/help-screen.ts b/lib/rust/ensogl/pack/js/src/runner/debug/help-screen.ts new file mode 100644 index 0000000000..b392b131df --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/debug/help-screen.ts @@ -0,0 +1,105 @@ +/** @file Help screen implementation. It displays a table of the provided entries. */ + +import * as dom from '../dom/dom' + +// ======================= +// === HelpScreenEntry === +// ======================= + +/** A single entry in the help screen view. If assigned with `href`, it will be displayed as a + * link. */ +export class HelpScreenEntry { + name: string + values: string[] + href?: string + + constructor(name: string, values: string[], href?: string) { + this.name = name + this.values = values + this.href = href + } +} + +// ================== +// === HelpScreen === +// ================== + +/** A visual help screen. It displays a table of the provided entries. */ +export class HelpScreen { + /** Displays a debug screen which allows the user to run one of predefined debug examples. */ + display(cfg: { title: string; headers: string[]; entries: HelpScreenEntry[] }) { + const padding = '8px' + const backgroundRadius = '8px' + const div = dom.newTopLevelDiv() + div.style.fontFamily = + '"SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif' + div.style.fontSize = '14px' + div.style.overflow = 'scroll' + const div2 = document.createElement('div') + div2.style.padding = '10px' + div.appendChild(div2) + + const title = document.createElement('div') + title.style.fontWeight = 'bold' + title.style.padding = '8px' + const content = document.createTextNode(cfg.title) + const table = document.createElement('table') + table.style.paddingTop = '20px' + table.style.borderCollapse = 'collapse' + table.style.maxWidth = '800px' + div2.style.position = 'absolute' + div2.style.zIndex = '1' + title.appendChild(content) + div2.appendChild(title) + div2.appendChild(table) + + const tr = document.createElement('tr') + for (const header of cfg.headers) { + const th = document.createElement('th') + th.innerText = header + th.style.textAlign = 'left' + th.style.padding = padding + tr.appendChild(th) + } + table.appendChild(tr) + + let rowWithBg = true + for (const entry of cfg.entries) { + const tr = document.createElement('tr') + table.appendChild(tr) + + const last = cfg.headers.length - 1 + for (let i = 0; i < cfg.headers.length; i++) { + const td = document.createElement('td') + td.style.padding = padding + tr.appendChild(td) + if (rowWithBg) { + td.style.background = '#00000010' + if (i == 0) { + td.style.borderTopLeftRadius = backgroundRadius + td.style.borderBottomLeftRadius = backgroundRadius + } else if (i == last) { + td.style.borderTopRightRadius = backgroundRadius + td.style.borderBottomRightRadius = backgroundRadius + } + } + if (i == 0) { + const a = document.createElement('a') + const linkText = document.createTextNode(entry.name) + a.appendChild(linkText) + a.title = entry.name + if (entry.href) { + a.href = entry.href + } + td.appendChild(a) + } else { + const value = entry.values[i - 1] + if (value != null) { + td.innerText = value + } + } + } + rowWithBg = !rowWithBg + } + } +} diff --git a/lib/rust/ensogl/pack/js/src/runner/debug/package-info.ts b/lib/rust/ensogl/pack/js/src/runner/debug/package-info.ts new file mode 100644 index 0000000000..a001119169 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/debug/package-info.ts @@ -0,0 +1,30 @@ +/** @file Package info. */ + +import { logger } from 'runner/log' + +// =================== +// === PackageInfo === +// =================== + +/** Package info. It contains info provided by the user of EnsoGL. */ +export class PackageInfo { + /** Constructor. */ + constructor(userProvidedInfo?: Record) { + const infoObject = userProvidedInfo ?? {} + Object.assign(this, infoObject) + } + + /** Display the current info in the console. */ + display() { + const entries = Object.entries(this) + if (entries.length > 0) { + logger.with('Package info.', () => { + for (const [key, value] of Object.entries(this)) { + if (value != null) { + logger.log(`${key}: ${value}`) + } + } + }) + } + } +} diff --git a/lib/rust/ensogl/pack/js/src/runner/dom/dom.ts b/lib/rust/ensogl/pack/js/src/runner/dom/dom.ts new file mode 100644 index 0000000000..0ca5071018 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/dom/dom.ts @@ -0,0 +1,21 @@ +/** @file DOM utilities. */ + +// ================== +// === HTML Utils === +// ================== + +/** Creates a new top-level div which occupy full size of its parent's space. */ +export function newTopLevelDiv(): HTMLDivElement { + const node = document.createElement('div') + node.style.width = '100%' + node.style.height = '100%' + document.body.appendChild(node) + return node +} + +/** Disable the context menu for the whole browser window. */ +export function disableContextMenu() { + document.body.addEventListener('contextmenu', e => { + e.preventDefault() + }) +} diff --git a/lib/rust/ensogl/pack/js/src/runner/dom/logo.ts b/lib/rust/ensogl/pack/js/src/runner/dom/logo.ts new file mode 100644 index 0000000000..7b21da7752 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/dom/logo.ts @@ -0,0 +1,119 @@ +/** @file Enso SVG logo generator. */ + +// ============== +// === Config === +// ============== + +/** Logo generator configuration. The fields affect the way the logo is drawn. */ +interface Config { + borderOffset?: number + borderWidth?: number + color?: string + compatibleMode?: boolean + rotation?: number + shapeErosion?: number + shapeSpikeCutoff?: number + showBorder?: boolean + size?: number +} + +// ============ +// === Logo === +// ============ + +/** The logo generator. */ +export class Logo { + borderOffset = 6 + borderWidth = 8 + color = '#FF0000' + compatibleMode = true + rotation = 0 + shapeErosion = -4 + shapeSpikeCutoff = 4 + showBorder = true + size = 64 + + ref: string + innerCircleRadius: number + shapeSpikeCutoffScaled: number + innerRadius: number + + constructor(config?: Config) { + this.borderOffset = config?.borderOffset ?? this.borderOffset + this.borderWidth = config?.borderWidth ?? this.borderWidth + this.color = config?.color ?? this.color + this.compatibleMode = config?.compatibleMode ?? this.compatibleMode + this.rotation = config?.rotation ?? this.rotation + this.shapeErosion = config?.shapeErosion ?? this.shapeErosion + this.shapeSpikeCutoff = config?.shapeSpikeCutoff ?? this.shapeSpikeCutoff + this.showBorder = config?.showBorder ?? this.showBorder + this.size = config?.size ?? this.size + + this.innerRadius = this.size / 2 - this.borderWidth - this.borderOffset + this.innerCircleRadius = this.innerRadius / 2 + this.shapeSpikeCutoffScaled = (this.shapeSpikeCutoff * this.size) / 64 + if (this.compatibleMode) { + this.ref = 'xlink:href' + } else { + this.ref = 'href' + } + } + + /** Generates the SVG code for the logo. */ + generate(): string { + const id = (name: string) => { + return `id="ensoLogo${name}"` + } + const idRef = (name: string) => { + return `${ref}="#ensoLogo${name}"` + } + const mask = (name: string) => { + return `mask="url(#ensoLogo${name})"` + } + const border = this.showBorder ? `` : '' + const size2 = this.size / 2 + const circleX = + this.borderWidth + this.borderOffset + this.innerCircleRadius + this.shapeErosion + const lCircleX = circleX - this.shapeSpikeCutoffScaled + const lCircleR = Math.max( + 0, + this.innerCircleRadius + this.shapeErosion + this.shapeSpikeCutoffScaled + ) + const rCircleX = circleX + 2 * this.innerCircleRadius + const rCircleR = Math.max(0, this.innerCircleRadius - this.shapeErosion) + const ref = this.ref + const xmlns = 'xmlns="http://www.w3.org/2000/svg"' + const viewBox = `viewBox="0 0 ${this.size} ${this.size}"` + const rotation = this.rotation + 35 + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + ` + } +} diff --git a/lib/rust/ensogl/pack/js/src/runner/dom/svg.ts b/lib/rust/ensogl/pack/js/src/runner/dom/svg.ts new file mode 100644 index 0000000000..7cd88bf1fb --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/dom/svg.ts @@ -0,0 +1,30 @@ +/** @file A set of utils for generating and modifying the SVG images. */ + +import * as math from 'runner/math' + +// =========== +// === SVG === +// =========== + +/** Defines a new SVG with the provided source. */ +export function newSvg(width: number, height: number, str: string): string { + return ` + + ${str} + ` +} + +/** Returns SVG code for an arc with a defined radius and angle. */ +export function arc(radius: number, endAngle: number): string { + let startAngle = 0 + if (endAngle < 0) { + startAngle = endAngle + endAngle = 0 + } + const start = math.polarToCartesian(radius, endAngle) + const end = math.polarToCartesian(radius, startAngle) + const largeArc = endAngle - startAngle <= 180 ? '0' : '1' + return `M 0 0 L ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}` +} diff --git a/lib/rust/ensogl/pack/js/src/runner/host.ts b/lib/rust/ensogl/pack/js/src/runner/host.ts new file mode 100644 index 0000000000..d8cedd83c7 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/host.ts @@ -0,0 +1,45 @@ +/** @file Utilities to work with the host environment, whether it is a browser of node. */ + +// ====================== +// === Host Utilities === +// ====================== + +/** Resolves to `true` if we are running the script in the browser. If it's node, resolves to + * `false`. */ +const browser = typeof window !== 'undefined' + +/** Resolves to `true` if we are running the script in node. If it's a browser, resolves to + * `false`. */ +const node = !browser + +/** The global object. In case of a browser, this is the window. In case of node, it's the built-in + * `global` object. */ +// const global = {} +global ??= window + +/** Returns the parameters passed in the URL query string. */ +function urlParams(): Record { + if (browser) { + const urlParams = new URLSearchParams(window.location.search) + return Object.fromEntries(urlParams.entries()) + } else { + return {} + } +} + +/** Export the value to the global scope (`global` in node and `window` in browser). */ +function exportGlobal(exports: Record) { + for (const [key, value] of Object.entries(exports)) { + /* eslint @typescript-eslint/no-unsafe-assignment: "off" */ + // @ts-expect-error + global[key] = value + } +} + +export default { + global, + exportGlobal, + browser, + node, + urlParams, +} diff --git a/lib/rust/ensogl/pack/js/src/runner/index.ts b/lib/rust/ensogl/pack/js/src/runner/index.ts new file mode 100644 index 0000000000..6e37fc8566 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/index.ts @@ -0,0 +1,533 @@ +/** @file Runner for the code generated by `ensogl-pack`. It is responsible for downloading and + * compiling WASM, displaying loading and help screens, and configuring the application. */ + +import * as dom from 'runner/dom/dom' +import * as name from 'runner/name' +import * as log from 'runner/log' +import * as wasm from 'runner/wasm' +import * as config from 'runner/config' +import * as array from 'runner/data/array' +import * as debug from 'runner/debug' + +import host from 'runner/host' +import { logger } from 'runner/log' +import { sortedWasmFunctions } from 'runner/wasm' + +// =============== +// === Exports === +// =============== + +export { log } +export { config } +export type { LogLevel } from 'runner/log/logger' + +export { logger, Logger, Consumer } from 'runner/log' +export { Param } from 'runner/config' + +// ============================== +// === Files to be downloaded === +// ============================== + +/** Files that are downloaded from server during app startup. */ +class Files { + /** Main JS file that is responsible for initializing and compiling WASM. */ + pkgJs: T + /** Main WASM file that contains the compiled WASM code. */ + pkgWasm: T + /** Precompiled shaders files. */ + shaders = new Shaders() + + constructor(pkgJs: T, pkgWasm: T) { + this.pkgJs = pkgJs + this.pkgWasm = pkgWasm + } + + async mapAndAwaitAll(f: (t: T) => Promise): Promise> { + const mapped = await Promise.all(this.toArray().map(f)) + const out = this.fromArray(mapped) + if (out != null) { + return out + } else { + log.panic() + } + } + + /** Converts the structure fields to an array. */ + toArray(): T[] { + return [this.pkgJs, this.pkgWasm, ...this.shaders.toArray()] + } + + /** Assign array values to the structure fields. The elements order should be the same as the + * output of the `toArray` function. */ + fromArray(array: S[]): Files | null { + const [pkgJs, pkgWasm, ...shaders] = array + if (pkgJs != null && pkgWasm != null) { + const files = new Files(pkgJs, pkgWasm) + files.shaders = this.shaders.fromArray(shaders) ?? new Shaders() + return files + } else { + return null + } + } +} + +/** Mapping between a shader identifier and precompiled shader sources. */ +class Shaders { + map = new Map>() + + async mapAndAwaitAll(f: (t: T) => Promise): Promise> { + const mapped = await Promise.all(this.toArray().map(f)) + const out = this.fromArray(mapped) + if (out != null) { + return out + } else { + log.panic() + } + } + + /** Converts the structure fields to an array. The shader names are not preserved. */ + toArray(): T[] { + return Array.from(this.map.values()).flatMap(shader => shader.toArray()) + } + + /** Assign array values to the structure fields. The elements order should be the same as the + * output of the `toArray` function. The shader names will be preserved and assigned to the + * input values in order. */ + fromArray(arr: S[]): Shaders | null { + const shaders = new Shaders() + const keys = Array.from(this.map.keys()) + const tuples = array.arrayIntoTuples(arr) + if (tuples == null) { + log.panic() + } else { + for (const [key, [vertex, fragment]] of array.zip(keys, tuples)) { + const shader = new Shader(vertex, fragment) + shaders.map.set(key, shader) + } + return shaders + } + } +} + +/** Precompiled shader sources */ +class Shader { + vertex: T + fragment: T + + constructor(vertex: T, fragment: T) { + this.vertex = vertex + this.fragment = fragment + } + + /** Converts the structure fields to an array. The shader names are not preserved. */ + toArray(): T[] { + return [this.vertex, this.fragment] + } +} + +// =========== +// === App === +// =========== + +/** Preferred frame time for the `Scheduler`. */ +const FRAME_TIME_MS = 16 + +/** A task scheduler used to run tasks in the next animation frame if the current animation frame is + * running too long. */ +class Scheduler { + done: Promise + doneResolve: () => void = () => {} + time: DOMHighResTimeStamp = 0 + tasks: (() => void)[] = [] + + constructor() { + this.done = new Promise(resolve => { + this.doneResolve = resolve + }) + } + + add(task: () => void) { + this.tasks.push(task) + } + + run(): Promise { + if (host.node) { + for (const task of this.tasks) { + task() + } + this.doneResolve() + } else { + this.onFrame() + } + return this.done + } + + onFrame() { + for (;;) { + const time = window.performance.now() + const delta = time - this.time + if (delta > FRAME_TIME_MS) { + break + } + const task = this.tasks.shift() + if (task != null) { + task() + } else { + this.doneResolve() + break + } + } + if (this.tasks.length === 0) { + this.doneResolve() + } else { + this.time = window.performance.now() + window.requestAnimationFrame(this.onFrame.bind(this)) + } + } +} + +// =========== +// === App === +// =========== + +/** The main application class. */ +export class App { + packageInfo: debug.PackageInfo + config: config.Config + wasm: any = null + loader: wasm.Loader | null = null + shaders: Shaders | null = null + wasmFunctions: string[] = [] + beforeMainEntryPoints = new Map() + mainEntryPoints = new Map() + initialized = false + + constructor(opts?: { + configExtension?: config.ExternalConfig + packageInfo?: Record + config?: Record + }) { + this.packageInfo = new debug.PackageInfo(opts?.packageInfo ?? {}) + this.config = new config.Config(opts?.configExtension) + const unrecognizedParams = this.config.resolve([opts?.config, host.urlParams()]) + if (unrecognizedParams) { + this.config.print() + this.showConfigOptions(unrecognizedParams) + } else { + this.initBrowser() + this.initialized = true + } + // Export the app to a global variable, so Rust can access it. + host.exportGlobal({ ensoglApp: this }) + } + + /** Registers the Rust function that extracts the shader definitions. */ + registerGetShadersRustFn(fn: GetShadersFn) { + logger.log(`Registering 'getShadersFn'.`) + rustGetShadersFn = fn + } + + /** Registers the Rust function that injects the shader definitions. */ + registerSetShadersRustFn(fn: SetShadersFn) { + logger.log(`Registering 'setShadersFn'.`) + rustSetShadersFn = fn + } + + /** Log the message on the remote server. */ + remoteLog(message: string, data: any) { + // TODO: Implement remote logging. This should be done after cloud integration. + } + + /** Initialize the browser. Set the background color, print user-facing warnings, etc. */ + initBrowser() { + if (host.browser) { + this.styleRoot() + dom.disableContextMenu() + if (this.config.params.debug.value) { + logger.log('Application is run in debug mode. Logs will not be hidden.') + } else { + this.printScamWarning() + log.router.hideLogs() + } + } + } + + /** Set the background color of the root element. */ + styleRoot() { + const root = document.getElementById('root') + if (root != null) { + root.style.backgroundColor = 'rgb(234,238,241)' + } + } + + /** Runs the application. It will initialize DOM elements, display a loader, run before main + * entry points and the main entry point. It will also list available entry points if the + * provided entry point is missing. */ + async run(): Promise { + if (!this.initialized) { + logger.log("App wasn't initialized properly. Skipping run.") + } else { + this.config.print() + await this.loadAndInitWasm() + await this.runEntryPoints() + } + } + + /** Compiles and runs the downloaded WASM file. */ + async compileAndRunWasm(pkgJs: string, wasm: Buffer | Response): Promise { + return await log.Task.asyncRunNoGroup('WASM compilation', async () => { + /* eslint @typescript-eslint/no-implied-eval: "off" */ + /* eslint @typescript-eslint/no-unsafe-assignment: "off" */ + const snippetsFn: any = Function( + // A hack to make Emscripten output load correctly (e.g. 'msdf-gen'). Emscripten + // generates a code which overrides `module.exports` after checking if the code + // is run in node. The check is performed by checking the values of `process`. + // Setting it to something else prevents the code from running. + `const process = "Overridden to prevent Emscripten from redefining module.exports." + const module = {} + ${pkgJs} + return module.exports` + )() + const out: unknown = await snippetsFn.init(wasm) + if (this.config.params.enableSpector.value) { + /* eslint @typescript-eslint/no-unsafe-member-access: "off" */ + /* eslint @typescript-eslint/no-unsafe-call: "off" */ + if (host.browser) { + const spectorModule: unknown = snippetsFn.spector() + console.log(spectorModule) + // @ts-ignore + const spector = new spectorModule.Spector() + // @ts-ignore + spector.displayUI() + } + } + return out + }) + } + + /** Download and load the WASM to memory. */ + async loadWasm() { + const loader = new wasm.Loader(this.config) + + const shadersUrl = this.config.params.shadersUrl.value + const shadersNames = await log.Task.asyncRunCollapsed( + 'Downloading shaders list.', + async () => { + const shadersListResponse = await fetch(`${shadersUrl}/list.txt`) + const shadersList = await shadersListResponse.text() + return shadersList.split('\n').filter(line => line.length > 0) + } + ) + + const files = new Files( + this.config.params.pkgJsUrl.value, + this.config.params.pkgWasmUrl.value + ) + for (const mangledName of shadersNames) { + const unmangledName = name.unmangle(mangledName) + const vertexUrl = `${shadersUrl}/${mangledName}.vertex.glsl` + const fragmentUrl = `${shadersUrl}/${mangledName}.fragment.glsl` + files.shaders.map.set(unmangledName, new Shader(vertexUrl, fragmentUrl)) + } + + const responses = await files.mapAndAwaitAll(url => fetch(url)) + const responsesArray = responses.toArray() + loader.load(responsesArray) + const downloadSize = loader.showTotalBytes() + const task = log.Task.startCollapsed(`Downloading application files (${downloadSize}).`) + void loader.done.then(() => task.end()) + + for (const file of files.toArray()) { + logger.log(`Downloading '${file}'.`) + } + + const pkgJs = await responses.pkgJs.text() + this.loader = loader + this.wasm = await this.compileAndRunWasm(pkgJs, responses.pkgWasm) + this.shaders = await responses.shaders.mapAndAwaitAll(t => t.text()) + } + + /** Loads the WASM binary and its dependencies. After the files are fetched, the WASM module is + * compiled and initialized. */ + async loadAndInitWasm() { + await this.loadWasm() + this.wasmFunctions = wasm.sortedWasmFunctions(this.wasm) + this.beforeMainEntryPoints = wasm.BeforeMainEntryPoint.fromNames(this.wasmFunctions) + this.mainEntryPoints = wasm.EntryPoint.fromNames(this.wasmFunctions) + this.packageInfo.display() + } + + /** Run all before main entry points. See the docs of `wasm.entryPoint` to learn more. */ + async runBeforeMainEntryPoints(): Promise { + const count = this.beforeMainEntryPoints.size + const scheduler = new Scheduler() + if (this.beforeMainEntryPoints.size) { + for (const entryPoint of this.beforeMainEntryPoints.values()) { + scheduler.add(() => { + log.Task.runTimed(`Running entry point '${entryPoint.displayName()}'.`, () => { + const fn = this.wasm[entryPoint.name()] + if (fn != null) { + fn() + } else { + logger.internalError(`Entry point not found.`) + } + }) + }) + } + } + const [time] = await log.Task.asyncRunCollapsedTimed( + `Running ${count} before main entry points.`, + async () => { + return await scheduler.run() + } + ) + this.checkBeforeMainEntryPointsTime(time) + } + + /** Check whether the time needed to run before main entry points is reasonable. Print a warning + * message otherwise. */ + checkBeforeMainEntryPointsTime(time: number) { + if (time > this.config.params.maxBeforeMainEntryPointsTimeMs.value) { + logger.error( + `Entry points took ${time} milliseconds to run. This is too long. ` + + 'Before main entry points should be used for fast initialization only.' + ) + } + } + + /** Run both before main entry points and main entry point. */ + async runEntryPoints() { + const entryPointName = this.config.params.entry.value + const entryPoint = this.mainEntryPoints.get(entryPointName) + if (entryPoint) { + await this.runBeforeMainEntryPoints() + if (this.shaders) this.setShaders(this.shaders.map) + if (this.loader) this.loader.destroy() + logger.log(`Running the main entry point '${entryPoint.displayName()}'.`) + const fn = this.wasm[entryPoint.name()] + if (fn != null) { + fn() + } else { + logger.internalError(`Entry point not found.`) + } + } else { + if (this.loader) this.loader.destroy() + this.showEntryPointSelector(entryPointName) + } + } + + /// Displays a debug screen which allows the user to run one of predefined debug examples. + showEntryPointSelector(unknownEntryPoint?: string) { + logger.log('Showing entry point selection help screen.') + const msg = unknownEntryPoint ? `Unknown entry point '${unknownEntryPoint}'. ` : '' + const title = msg + 'Available entry points:' + const entries = Array.from(this.mainEntryPoints.values()).map(entryPoint => { + // FIXME: Currently, this does not work. It should be fixed by wasm-bindgen or wasm-pack + // team. See: https://github.com/rustwasm/wasm-bindgen/issues/3224 + /* eslint @typescript-eslint/no-unsafe-assignment: "off" */ + const docsFn = this.wasm[entryPoint.docsFnName()] + let description = 'No description.' + if (docsFn) { + const rustDocs = docsFn() + if (rustDocs) { + description = rustDocs + } + } + const href = '?entry=' + entryPoint.strippedName + return new debug.HelpScreenEntry(entryPoint.strippedName, [description], href) + }) + + const headers = ['Name', 'Description'] + new debug.HelpScreen().display({ title, headers, entries }) + } + + showConfigOptions(unknownConfigOptions?: string[]) { + logger.log('Showing config options help screen.') + const msg = unknownConfigOptions + ? `Unknown config options: '${unknownConfigOptions.join(', ')}'. ` + : '' + const title = msg + 'Available options:' + const entries = Array.from(Object.entries(this.config.params)).map(([key, option]) => { + return new debug.HelpScreenEntry(key, [option.description, String(option.default)]) + }) + const headers = ['Name', 'Description', 'Default'] + new debug.HelpScreen().display({ title, headers, entries }) + } + + /** Print the warning for the end user that they should not copy any code to the console. */ + printScamWarning() { + const headerCss = ` + color : white; + background : crimson; + display : block; + border-radius : 8px; + font-weight : bold; + padding: 10px 20px 10px 20px; + ` + const headerCss1 = headerCss + 'font-size : 46px;' + const headerCss2 = headerCss + 'font-size : 20px;' + const msgCSS = 'font-size:16px;' + + const msg1 = + 'This is a browser feature intended for developers. If someone told you to ' + + 'copy-paste something here, it is a scam and will give them access to your ' + + 'account and data.' + const msg2 = + 'See https://github.com/enso-org/enso/blob/develop/docs/security/selfxss.md for more ' + + 'information.' + console.log('%cStop!', headerCss1) + console.log('%cYou may be victim of a scam!', headerCss2) + console.log('%c' + msg1, msgCSS) + console.log('%c' + msg2, msgCSS) + } + + /* Get not optimized shaders from WASM. */ + getShaders(): Map | null { + return log.Task.run('Getting shaders from Rust.', () => { + if (!rustGetShadersFn) { + logger.error('The Rust shader extraction function was not registered.') + return null + } else { + const result = rustGetShadersFn() + logger.log(`Got ${result.size} shader definitions.`) + return result + } + }) + } + + /* Set optimized shaders in WASM. */ + setShaders(map: Map) { + log.Task.runCollapsed(`Sending ${map.size} shaders to Rust.`, () => { + if (!rustSetShadersFn) { + logger.error('The Rust shader injection function was not registered.') + } else { + logger.log(`Setting ${map.size} shader definitions.`) + rustSetShadersFn(map) + } + }) + } +} + +// ========================== +// === App Initialization === +// ========================== + +type GetShadersFn = () => Map +type SetShadersFn = (map: Map) => void + +let rustGetShadersFn: null | GetShadersFn = null +let rustSetShadersFn: null | SetShadersFn = null + +/** Registers the Rust function that extracts the shader definitions. */ +function registerGetShadersRustFn(fn: GetShadersFn) { + logger.log(`Registering 'getShadersFn'.`) + rustGetShadersFn = fn +} + +/** Registers the Rust function that injects the shader definitions. */ +function registerSetShadersRustFn(fn: SetShadersFn) { + logger.log(`Registering 'setShadersFn'.`) + rustSetShadersFn = fn +} + +host.exportGlobal({ registerGetShadersRustFn, registerSetShadersRustFn }) diff --git a/lib/rust/ensogl/pack/js/src/runner/log.ts b/lib/rust/ensogl/pack/js/src/runner/log.ts new file mode 100644 index 0000000000..0a96c69bc8 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/log.ts @@ -0,0 +1,12 @@ +/** @file Logging utilities. */ + +export * from './log/logger' +export * from './log/task' +export * from './log/router' + +/** Panics with the provided message. + * @throws Will throw an error. Use it only if there is no other option. */ +export function panic(msg?: string): never { + const suffix = msg ? ` ${msg}` : '' + throw new Error(`Internal Error.${suffix}`) +} diff --git a/lib/rust/ensogl/pack/js/src/runner/log/logger.ts b/lib/rust/ensogl/pack/js/src/runner/log/logger.ts new file mode 100644 index 0000000000..2adae81355 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/log/logger.ts @@ -0,0 +1,286 @@ +/** @file Logger capable of displaying nicely formatted logs in the browser and in the console. + * Also, it allows redirecting logs to different outputs, so you can plug in external sinks for the + * logs. */ + +import host from 'runner/host' + +// ============= +// === Utils === +// ============= + +/** Checks whether the provided value is an Object. It is used to determine whether the value should + * be printed using `JSON.stringify`. */ +function isObject(value: any): boolean { + return typeof value === 'object' && !Array.isArray(value) && value !== null +} + +// ============== +// === Colors === +// ============== + +/* eslint @typescript-eslint/no-extraneous-class: "off" */ +class Colors { + static resetCode = '\x1b[0m' + static redCode = '\x1b[31m' + static orangeCode = '\x1b[33m' + static red(text: string): string { + return Colors.redCode + text + Colors.resetCode + } + static orange(text: string): string { + return Colors.orangeCode + text + Colors.resetCode + } + static boldStart(): string { + return '\x1b[1m' + } + static reset(): string { + return Colors.resetCode + } + static level(level: number, text: string): string { + return Colors.levelStart(level) + text + Colors.resetCode + } + static levelStart(level: number): string { + switch (level) { + case 0: + return `\x1b[38;5;155m` + case 1: + return `\x1b[38;5;85m` + case 2: + return `\x1b[38;5;51m` + default: + return `\x1b[38;5;64m` + } + } +} + +// ================ +// === Consumer === +// ================ + +export type LogLevel = 'trace' | 'log' | 'warn' | 'error' + +/** Consumer interface for `Logger`. It can be used to redirect logs to external sinks. */ +export abstract class Consumer { + abstract message(fn: LogLevel, ...args: unknown[]): void + + /** Start a group and log a message. */ + abstract group(...args: unknown[]): void + + /** Start a group and log a message. */ + abstract groupCollapsed(...args: unknown[]): void + + /** Log a message and end the last opened group. */ + abstract groupEnd(...args: unknown[]): void + + /** Log a message. */ + log(...args: unknown[]) { + this.message('log', ...args) + } + + /** Log a warning. */ + warn(...args: unknown[]) { + this.message('warn', ...args) + } + + /** Log an error. */ + error(...args: unknown[]) { + this.message('error', ...args) + } + + /** Log an internal error. */ + internalError(...args: unknown[]) { + this.message('error', 'Internal error.', ...args) + } + + /** Start a group, log a message, evaluate the provided function, and end the group. */ + with(message: string, f: () => T): T { + this.group(message) + const out = f() + this.groupEnd() + return out + } + + /** Start a collapsed group, log a message, evaluate the provided function, and end the + * group. */ + withCollapsed(message: string, f: () => T): T { + this.groupCollapsed(message) + const out = f() + this.groupEnd() + return out + } + + /** Start a group, log a message, evaluate the provided async function, and end the group. */ + async asyncWith(message: string, f: () => Promise): Promise { + this.group(message) + const out = await f() + this.groupEnd() + return out + } + + /** Start a collapsed group, log a message, evaluate the provided async function, and end the + * group. */ + async asyncWithCollapsed(message: string, f: () => Promise): Promise { + this.groupCollapsed(message) + const out = await f() + this.groupEnd() + return out + } +} + +// ============== +// === Logger === +// ============== + +/** Logger capable of displaying nicely formatted logs in the browser and in the console. */ +export class Logger extends Consumer { + private consumers: Consumer[] = [] + + /** Add a new consumer. All logs will be redirected to every attached consumer. */ + addConsumer(consumer: Consumer) { + this.consumers.push(consumer) + } + + /** Generic logging function. The first parameters is used to determine the log level. */ + message(fn: LogLevel, ...args: unknown[]) { + for (const consumer of this.consumers) { + consumer.message(fn, ...args) + } + } + + /** Start a log group. */ + group(...args: unknown[]) { + for (const consumer of this.consumers) { + consumer.group(...args) + } + } + + /** Start a collapsed log group. */ + groupCollapsed(...args: unknown[]) { + for (const consumer of this.consumers) { + consumer.groupCollapsed(...args) + } + } + + /** End the last log group. */ + groupEnd(...args: unknown[]) { + for (const consumer of this.consumers) { + consumer.groupEnd(...args) + } + } +} + +// =============== +// === Console === +// =============== + +/** Pretty-printer for some object types for `JSON.stringify`. */ +function replacer(key: string, value: unknown): unknown { + if (value instanceof Map) { + return { + dataType: 'Map', + value: Array.from(value.entries()), + } + } else { + return value + } +} + +/** The console log consumer. If attached to `Logger`, it prints the incoming logs to the + * console. */ +export class Console extends Consumer { + private indentLvl = 0 + + message(fn: LogLevel, ...args: unknown[]) { + const strArgs = args.map(arg => { + if (isObject(arg)) { + return JSON.stringify(arg, replacer, 2) + } else { + return String(arg) + } + }) + const c: globalThis.Console = console + if (host.browser) { + c[fn](...strArgs) + } else { + let color: null | 'orange' | 'red' + switch (fn) { + case 'warn': + color = 'orange' + break + case 'error': + color = 'red' + break + default: + color = null + break + } + /* eslint @typescript-eslint/no-unsafe-return: "off" */ + /* eslint @typescript-eslint/no-unsafe-call: "off" */ + /* eslint @typescript-eslint/no-unsafe-assignment: "off" */ + // @ts-expect-error + const coloredArgs: string[] = color ? strArgs.map(arg => Colors[color](arg)) : strArgs + if (this.indentLvl > 0) { + const indent = this.indent() + const indentedArgs = coloredArgs.map(arg => arg.replaceAll('\n', `\n${indent}`)) + c.log(this.indentShorter(), ...indentedArgs) + } else { + c.log(...strArgs) + } + } + } + + group(...args: unknown[]) { + if (host.browser) { + console.group(...args) + } else { + const styleStart = `${Colors.boldStart()}${Colors.levelStart(this.indentLvl)}` + console.log(`${this.indent()}${styleStart}╭`, ...args, Colors.reset()) + } + this.indentLvl += 1 + } + + groupCollapsed(...args: unknown[]) { + if (host.browser) { + console.groupCollapsed(...args) + this.indentLvl += 1 + } else { + this.group(...args) + } + } + + groupEnd(...args: unknown[]) { + if (this.indentLvl > 0) { + this.indentLvl -= 1 + if (host.browser) { + if (args.length > 0) { + console.log(...args) + } + console.groupEnd() + } else { + const styleStart = `${Colors.levelStart(this.indentLvl)}` + console.log(`${this.indent()}${styleStart}╰`, ...args) + } + } else { + this.log(...args) + } + } + + private indent(): string { + let out = '' + for (let i = 0; i < this.indentLvl; i++) { + const box = Colors.level(i, '│') + out += `${box} ` + } + return out + } + + private indentShorter(): string { + return this.indent().slice(0, -1) + } +} + +// =============================== +// === Default Logger Instance === +// =============================== + +export const logger = new Logger() +logger.addConsumer(new Console()) diff --git a/lib/rust/ensogl/pack/js/src/runner/log/router.ts b/lib/rust/ensogl/pack/js/src/runner/log/router.ts new file mode 100644 index 0000000000..1c1f917da9 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/log/router.ts @@ -0,0 +1,92 @@ +/** @file Log router allowing hiding all logs in the web console and showing them on demand. It is + * used for disabling logs in the production build in order to prevent cluttering the console. */ + +import host from 'runner/host' + +// ============== +// === Router === +// ============== + +const consoleLogNames = [ + 'log', + 'info', + 'debug', + 'warn', + 'error', + 'group', + 'groupCollapsed', + 'groupEnd', +] satisfies (keyof Console)[] + +// FIXME: fix Rust `autoFlush` handling +/** Router for logs. It is used to hide the incoming logs and show them on demand. It is used to + * unclutter the logs view when the app is run by end-user. */ +class Router { + private buffer: { name: string; args: unknown[] }[] + private readonly console: Record void> + autoFlush: boolean + + constructor() { + this.buffer = [] + this.console = {} + this.autoFlush = true + for (const name of consoleLogNames) { + this.console[name] = console[name] + console[name] = (...args: unknown[]) => { + this.consume(name, args) + } + } + } + + /** set the auto-flush to on. All subsequent logs will not be buffered and will be immediately + * redirected to log consumers. */ + private autoFlushOn() { + this.autoFlush = true + for (const { name, args } of this.buffer) { + const fn = this.console[name] + if (fn) { + fn(...args) + } else { + console.error(`Unknown log name '${name}'.`) + } + } + this.buffer = [] + } + + /** Consume a new message. Add it to the message buffer if auto-flush is off. Print it with + * logger otherwise. */ + private consume(name: string, args: unknown[]) { + if (this.autoFlush) { + const fn = this.console[name] + if (fn) { + fn(...args) + } else { + console.error(`Unknown log name '${name}'.`) + } + } else { + this.buffer.push({ name, args }) + } + } + + /** Hide all subsequent logs until the `showLogs` method is called. */ + hideLogs() { + console.log('All subsequent logs will be hidden. Eval `showLogs()` to reveal them.') + this.autoFlush = false + } + + /** Display all hidden logs and do not hide any subsequent logs. */ + showLogs() { + this.autoFlushOn() + } +} + +// =============== +// === Exports === +// =============== + +export const router = new Router() + +host.exportGlobal({ + hideLogs: router.hideLogs.bind(router), + showLogs: router.showLogs.bind(router), +}) diff --git a/lib/rust/ensogl/pack/js/src/runner/log/task.ts b/lib/rust/ensogl/pack/js/src/runner/log/task.ts new file mode 100644 index 0000000000..6b39a55403 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/log/task.ts @@ -0,0 +1,177 @@ +/** @file A logging utility which groups subsequent operations in nicely formatted groups and logs + * their evaluation time. */ + +import { logger } from 'runner/log/logger' + +// ============ +// === Task === +// ============ + +/** A logging utility which groups subsequent operations in nicely formatted groups and logs their + * evaluation time. */ +export class Task { + startTime = 0 + endTime = 0 + constructor(public message: string) {} + + private startBody() { + this.startTime = performance.now() + } + + private endBody(): [number, number] { + this.endTime = performance.now() + const ms = this.endTime - this.startTime + let msRounded = Math.round(ms * 10) / 10 + if (msRounded == 0) { + msRounded = Math.round(ms * 100) / 100 + } + if (msRounded == 0) { + msRounded = Math.round(ms * 1000) / 1000 + } + return [ms, msRounded] + } + + /** Start the task. You have to explicitly call the `end` method to finish this task. If + * possible, use the `with` method instead. */ + start() { + logger.group(`${this.message}`) + this.startBody() + } + + /** Start the task and display subsequent logs in a collapsed group. You have to explicitly call + * the `end` method to finish this task. If possible, use the `withCollapsed` method instead. */ + startCollapsed() { + logger.groupCollapsed(`${this.message}`) + this.startBody() + } + + /** Start the task but do not group subsequent logs. You have to explicitly call the + * `endNoGroup` method to finish this task. If possible, use the `withNoGroup` method + * instead. */ + startNoGroup() { + logger.log(`Started ${this.message}.`) + this.startBody() + } + + /** End the previously started task. If possible use the `with*` function family instead. */ + end(): number { + const [ms, msRounded] = this.endBody() + logger.groupEnd(`Done in ${msRounded} ms.`) + return ms + } + + /** End the previously started no-group task. If possible use the `with*` function family + * instead. */ + endNoGroup(): number { + const [ms, msRounded] = this.endBody() + logger.log(`Finished ${this.message} in ${msRounded} ms.`) + return ms + } + + /** Start the task. You have to explicitly call the `end` method to finish this task. If + * possible, use the `with` method instead. */ + static start(message: string): Task { + const task = new Task(message) + task.start() + return task + } + + /** Start the task and display subsequent logs in a collapsed group. You have to explicitly call + * the `end` method to finish this task. If possible, use the `withCollapsed` method instead. */ + static startCollapsed(message: string): Task { + const task = new Task(message) + task.startCollapsed() + return task + } + + /** Start the task but do not group subsequent logs. You have to explicitly call the + * `endNoGroup` method to finish this task. If possible, use the `withNoGroup` method + * instead. */ + static startNoGroup(message: string): Task { + const task = new Task(message) + task.startNoGroup() + return task + } + + /** Start the task, evaluate the provided function, and end the task. */ + static run(message: string, f: () => T): T { + const task = Task.start(message) + const out = f() + task.end() + return out + } + + /** Start the task, hide all subsequent logs in a collapsed group, evaluate the provided + * function, and end the task. */ + static runCollapsed(message: string, f: () => T): T { + const task = Task.startCollapsed(message) + const out = f() + task.end() + return out + } + + /** Start the task, evaluate the provided async function, and end the task. */ + static async asyncRun(message: string, f: () => Promise): Promise { + const task = Task.start(message) + const out = await f() + task.end() + return out + } + + /** Start the task, hide all subsequent logs in a collapsed group, evaluate the provided + * async function, and end the task. */ + static async asyncRunCollapsed(message: string, f: () => Promise): Promise { + const task = Task.startCollapsed(message) + const out = await f() + task.end() + return out + } + + /** Start the task, evaluate the provided async function, and end the task. Do not group + * subsequent logs. */ + static async asyncRunNoGroup(message: string, f: () => Promise): Promise { + const task = Task.startNoGroup(message) + const out = await f() + task.endNoGroup() + return out + } + + /** Start the task, evaluate the provided function, and end the task. Return the function result + * together with the time information. */ + static runTimed(message: string, f: () => T): [number, T] { + const task = Task.start(message) + const out = f() + const ms = task.end() + return [ms, out] + } + + /** Start the task, hide all subsequent logs in a collapsed group, evaluate the provided + * function, and end the task. Return the function result together with the time information. */ + static runCollapsedTimed(message: string, f: () => T): [number, T] { + const task = Task.startCollapsed(message) + const out = f() + const ms = task.end() + return [ms, out] + } + + /** Start the task, evaluate the provided async function, and end the task. Return the function + * result together with the time information. */ + static async asyncRunTimed(message: string, f: () => Promise): Promise<[number, T]> { + const task = Task.start(message) + const out = await f() + const ms = task.end() + return [ms, out] + } + + /** Start the task, hide all subsequent logs in a collapsed group, evaluate the provided async + * function, and end the task. Return the function result together with the time information. */ + static async asyncRunCollapsedTimed( + message: string, + f: () => Promise + ): Promise<[number, T]> { + const task = Task.startCollapsed(message) + const out = await f() + const ms = task.end() + return [ms, out] + } +} diff --git a/lib/rust/ensogl/pack/js/src/runner/math.ts b/lib/rust/ensogl/pack/js/src/runner/math.ts new file mode 100644 index 0000000000..ea340eafdb --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/math.ts @@ -0,0 +1,19 @@ +/** @file Common math operations. */ + +// ============ +// === Math === +// ============ + +/** Converts the polar coordinates to cartesian ones. */ +export function polarToCartesian(radius: number, angleDegrees: number): { x: number; y: number } { + const angle = ((angleDegrees - 90) * Math.PI) / 180.0 + return { + x: radius * Math.cos(angle), + y: radius * Math.sin(angle), + } +} + +/** Format bytes as megabytes with a single precision number. */ +export function formatMb(bytes: number): number { + return Math.round((10 * bytes) / (1024 * 1024)) / 10 +} diff --git a/lib/rust/ensogl/pack/js/src/runner/name.ts b/lib/rust/ensogl/pack/js/src/runner/name.ts new file mode 100644 index 0000000000..2a04aaa952 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/name.ts @@ -0,0 +1,56 @@ +/** @file Naming utilities. Defines name mangling and un-mangling functions allowing passing complex + * UTF-8 names between Rust and JS. */ + +export const NAME_REGEX = new RegExp(String.raw`(?__)|(_(?[0-9]+)_)`, 'g') + +/** Checks whether the provided string is a single number digit. */ +export function isDigit(char: string): boolean { + const code = char.charCodeAt(0) + return code >= 48 && code <= 57 +} + +/** Checks whether the provided string is a single lowercase character. */ +export function isLowerChar(char: string): boolean { + const code = char.charCodeAt(0) + return code >= 97 && code <= 122 +} + +/** Checks whether the provided string is a single uppercase character. */ +export function isUpperChar(char: string): boolean { + const code = char.charCodeAt(0) + return code >= 65 && code <= 90 +} + +/** Checks whether the provided string is a single digit, lowercase character, or uppercase + * character. */ +export function isBasicChar(char: string): boolean { + return isDigit(char) || isLowerChar(char) || isUpperChar(char) +} + +/** Un-mangles the name. See docs of `mangle` to learn more. */ +export function unmangle(name: string): string { + return name.replace(NAME_REGEX, (...args) => { + /* eslint @typescript-eslint/no-unsafe-assignment: "off" */ + const groups: { underscore: string; specialChar: string } = args.at(-1) + if (groups.underscore) { + return '_' + } else { + return String.fromCharCode(parseInt(groups.specialChar)) + } + }) +} + +/** Mangles the name. Converts all `_` to `__` and all non-ASCII characters to `__`. */ +export function mangle(name: string): string { + let result = '' + for (const char of name) { + if (isBasicChar(char)) { + result += char + } else if (char === '_') { + result += '__' + } else { + result += `_${char.charCodeAt(0)}_` + } + } + return result +} diff --git a/lib/rust/ensogl/pack/js/src/runner/wasm.ts b/lib/rust/ensogl/pack/js/src/runner/wasm.ts new file mode 100644 index 0000000000..6fdd6d4371 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/wasm.ts @@ -0,0 +1,4 @@ +/** @file WASM utilities. */ + +export * from './wasm/loader' +export * from './wasm/entry-point' diff --git a/lib/rust/ensogl/pack/js/src/runner/wasm/entry-point.ts b/lib/rust/ensogl/pack/js/src/runner/wasm/entry-point.ts new file mode 100644 index 0000000000..76d318e33d --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/wasm/entry-point.ts @@ -0,0 +1,150 @@ +/** @file WASM entry point definition. An entry point is a WASM function exported from Rust that was + * marked as an entry point. */ + +import * as name from '../name' +import { logger } from '../log/logger' + +// ================= +// === Constants === +// ================= + +export const MAIN_ENTRY_POINT_PREFIX = 'entry_point_' +export const BEFORE_MAIN_ENTRY_POINT_PREFIX = 'before_main_entry_point_' + +// ================== +// === EntryPoint === +// ================== + +/** A WASM entry point, a function exported by wasm-bindgen in Rust. There are two types of entry + * points, before main entry points and main entry points. The former are meant to be all run before + * the chosen main entry point is run. */ +export class EntryPoint { + prefix = MAIN_ENTRY_POINT_PREFIX + constructor(public strippedName: string) {} + + /** The original name of the WASM function. */ + name(): string { + return this.prefix + this.strippedName + } + + /** The name of the function used to obtain documentation for this entry point. */ + docsFnName(): string { + return `docs_of_${this.name()}` + } + + /** Not mangled name, easy to read by the user. WASM function names exported from Rust are + * mangled to contain the path and location information. This converts them to a form of + * 'file/path/from/repo/root.rs:line:column'. */ + displayName(): string { + return name.unmangle(this.strippedName) + } + + /** Try parsing the name as the provided entry point type. If the name does not start with the + * provided prefix, return `null`. */ + static tryFrom(fullName: string): EntryPoint | null { + const prefix = MAIN_ENTRY_POINT_PREFIX + if (fullName.startsWith(prefix)) { + return new EntryPoint(fullName.substring(prefix.length)) + } else { + return null + } + } + + /** Filter the provided names list and return array of valid entry points. The entry points will + * be sorted in the lexicographical order. */ + static fromNamesAsArray(names: string[]): EntryPoint[] { + const arr = names + .map(n => EntryPoint.tryFrom(n)) + .filter((n): n is EntryPoint => n != null) + .reduce((arr, n) => { + arr.push(n) + return arr + }, new Array()) + arr.sort((a, b) => a.strippedName.localeCompare(b.strippedName)) + return arr + } + + /** Filter the provided names list and return map of valid entry points. The entry points will + * be sorted in the lexicographical order. */ + static fromNames(names: string[]): Map { + const arr = EntryPoint.fromNamesAsArray(names) + return new Map(arr.map(n => [n.strippedName, n])) + } +} + +// ============================ +// === BeforeMainEntryPoint === +// ============================ + +export class BeforeMainEntryPoint extends EntryPoint { + constructor(strippedName: string, public priority: number) { + super(strippedName) + this.prefix = BEFORE_MAIN_ENTRY_POINT_PREFIX + } + + override name(): string { + return `${this.prefix}${this.priority}_${this.strippedName}` + } + + override displayName(): string { + return `[${this.priority}] ${super.displayName()}` + } + + static override tryFrom(fullName: string): EntryPoint | null { + const prefix = BEFORE_MAIN_ENTRY_POINT_PREFIX + if (fullName.startsWith(prefix)) { + const suffix = fullName.substring(prefix.length) + const underscoreIndex = suffix.indexOf('_') + let splitIndex = prefix.length + let priority = null + if (underscoreIndex !== -1) { + splitIndex += underscoreIndex + 1 + const newPriority = parseInt(suffix.substring(0, underscoreIndex)) + if (!isNaN(newPriority)) { + priority = newPriority + } + } + if (priority == null) { + logger.log( + `Invalid before main entry point name. ` + + `No priority prefix found in '${fullName}'.` + ) + priority = 0 + } + const strippedName = fullName.substring(splitIndex) + return new BeforeMainEntryPoint(strippedName, priority) + } else { + return null + } + } + + static override fromNamesAsArray(names: string[]): BeforeMainEntryPoint[] { + const arr = names + .map(n => BeforeMainEntryPoint.tryFrom(n)) + .filter((n): n is BeforeMainEntryPoint => n != null) + .reduce((arr, n) => { + arr.push(n) + return arr + }, new Array()) + arr.sort((a, b) => a.strippedName.localeCompare(b.strippedName)) + return arr + } + + static override fromNames(names: string[]): Map { + const arr = BeforeMainEntryPoint.fromNamesAsArray(names) + arr.sort((a, b) => a.priority - b.priority) + return new Map(arr.map(n => [n.strippedName, n])) + } +} + +// ============= +// === Utils === +// ============= + +/** Return list of all WASM functions. The functions will be sorted by name in order to be sure that + * the runtime results are always reproducible between builds. */ +export function sortedWasmFunctions(wasm: any): string[] { + const names = Object.getOwnPropertyNames(wasm) + names.sort() + return names +} diff --git a/lib/rust/ensogl/pack/js/src/runner/wasm/loader.ts b/lib/rust/ensogl/pack/js/src/runner/wasm/loader.ts new file mode 100644 index 0000000000..55df0058b5 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runner/wasm/loader.ts @@ -0,0 +1,431 @@ +/** @file Files loader. Displays a loading spinner and reports the current download progress. */ + +import * as animation from 'runner/animation' +import * as html_utils from 'runner/dom/dom' +import * as math from 'runner/math' +import * as svg from 'runner/dom/svg' +import { Config } from 'runner/config' +import { Logo } from 'runner/dom/logo' +import { logger } from 'runner/log' + +// ========================= +// === ProgressIndicator === +// ========================= + +const loaderColor = '#3c3c3c' +const ghostColor = '#00000020' +const topLayerIndex = '1000' + +/** Visual representation of the loader. */ +class ProgressIndicator { + dom: HTMLDivElement + track: HTMLElement + indicator: HTMLElement + progressIndicatorMask: HTMLElement + loaderTrackEndPoint: HTMLElement + logo: HTMLElement + center: HTMLElement + initialized: Promise + destroyed: boolean + ringInnerRadius: number + ringWidth: number + + animatedValue = 0 + targetValue = 0 + minProgressSize = 0.1 + + constructor(cfg: Config) { + this.ringInnerRadius = 48 + this.ringWidth = 12 + + this.dom = html_utils.newTopLevelDiv() + this.dom.id = 'loader' + this.dom.style.position = 'fixed' + this.dom.style.top = '0' + this.dom.style.left = '0' + this.dom.style.zIndex = topLayerIndex + this.dom.style.background = 'white' + this.dom.style.opacity = '1' + + const center = document.createElement('div') + center.style.width = '100%' + center.style.height = '100%' + center.style.display = 'flex' + center.style.justifyContent = 'center' + center.style.alignItems = 'center' + this.dom.appendChild(center) + this.center = center + + const outerRadius = this.ringInnerRadius + this.ringWidth + const size = outerRadius * 2 + + const progressBarSvg = this.initSvg() + const logo = document.createElement('div') + const progressBar = document.createElement('div') + progressBar.style.position = 'absolute' + logo.innerHTML = new Logo({ + size, + color: loaderColor, + showBorder: false, + borderWidth: this.ringWidth, + borderOffset: 8, + }).generate() + this.logo = logo + progressBar.innerHTML = progressBarSvg + center.appendChild(progressBar) + center.appendChild(logo) + + // @ts-expect-error + this.track = document.getElementById('loaderTrack') + // @ts-expect-error + this.indicator = document.getElementById('loaderIndicator') + // @ts-expect-error + this.progressIndicatorMask = document.getElementById('progressIndicatorMask') + // @ts-expect-error + this.loaderTrackEndPoint = document.getElementById('loaderTrackEndPoint') + + this.set(0) + this.setIndicatorOpacity(0) + + if (cfg.params.useLoader.value) { + this.initialized = Promise.all([ + this.animateShow(), + this.animateShowLogo(), + this.animateProgress(), + ]).then(() => {}) + } else { + this.initialized = new Promise(resolve => { + resolve() + }) + } + this.animateRotation() + this.destroyed = false + } + + /** Initialize the SVG view. */ + initSvg(): string { + const outerRadius = this.ringInnerRadius + this.ringWidth + const ringCenterRadius = this.ringInnerRadius + this.ringWidth / 2 + const size = outerRadius * 2 + + return svg.newSvg( + size, + size, + ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + ) + } + + /** Destroy the component. Remove it from the stage and destroy attached callbacks. */ + destroy() { + const self = this + void this.initialized.then(() => { + void this.animateHide().then(() => { + const parent = self.dom.parentNode + if (parent) { + parent.removeChild(self.dom) + } + self.destroyed = true + }) + }) + } + + set(value: number) { + this.targetValue = value + } + + displayProgress(value: number) { + const minAngle = 0 + const maxAngle = 359 + const outerRadius = this.ringInnerRadius + this.ringWidth + const size = outerRadius * 2 + const clampedValue = Math.min(Math.max(value, 0), 1) + const ringCenterRadius = this.ringInnerRadius + this.ringWidth / 2 + const angleSpan = maxAngle - minAngle + const maskAngle = maxAngle - ((1 - clampedValue) * angleSpan - minAngle) + const cornerPos = math.polarToCartesian(ringCenterRadius, maskAngle) + this.progressIndicatorMask.setAttribute('d', svg.arc(size, maskAngle)) + this.loaderTrackEndPoint.setAttribute('cx', `${cornerPos.x}`) + this.loaderTrackEndPoint.setAttribute('cy', `${cornerPos.y}`) + } + + /** Set the opacity of the loader. */ + setIndicatorOpacity(val: number) { + this.center.style.opacity = `${val}` + } + + /** Set the opacity of the loader. */ + setOpacity(val: number) { + this.dom.style.opacity = `${val}` + } + + /** Set the rotation of the loader (angles). */ + setRotation(val: number) { + this.track.setAttribute('transform', `rotate(${val},0,0)`) + } + + /** Start show animation. It is used after the loader is created. */ + animateShow(): Promise { + const self = this + const startTime = window.performance.now() + return new Promise(function (resolve) { + const step = (time: DOMHighResTimeStamp) => { + const opacitySampler = Math.min((time - startTime) / (1000 * 1), 1) + self.setIndicatorOpacity(animation.easeInOutQuad(opacitySampler)) + if (opacitySampler < 1) { + window.requestAnimationFrame(step) + } else { + resolve() + } + } + window.requestAnimationFrame(step) + }) + } + + /** Start the progress bar animation. The progress bar grows smoothly even if the data is + * received in chunks. */ + animateProgress(): Promise { + const self = this + let lastTime = window.performance.now() + self.displayProgress(self.minProgressSize) + return new Promise(function (resolve) { + const step = (time: DOMHighResTimeStamp) => { + const timeDiff = time - lastTime + lastTime = time + if (self.animatedValue < self.targetValue) { + self.animatedValue = Math.min( + self.targetValue, + self.animatedValue + timeDiff / 1000 + ) + self.displayProgress( + self.minProgressSize + (1 - self.minProgressSize) * self.animatedValue + ) + } + if (self.animatedValue < 1) { + window.requestAnimationFrame(step) + } else { + resolve() + } + } + window.requestAnimationFrame(step) + }) + } + + /** Start the logo show animation. */ + animateShowLogo(): Promise { + const self = this + const startTime = window.performance.now() + const outerRadius = this.ringInnerRadius + this.ringWidth + const size = outerRadius * 2 + return new Promise(function (resolve) { + const step = (time: DOMHighResTimeStamp) => { + const opacitySampler = Math.min((time - startTime) / (1000 * 2), 1) + const anim = animation.elasticInOut + self.logo.innerHTML = new Logo({ + size, + color: loaderColor, + showBorder: false, + borderWidth: self.ringWidth, + shapeSpikeCutoff: 1 + 6 * anim.amplitude(1).period(1)(opacitySampler), + rotation: 100 - 100 * anim.amplitude(0.5).period(1)(opacitySampler), + borderOffset: 20 * (1 - anim.amplitude(1).period(0.4)(opacitySampler)) + 8, + shapeErosion: 30 * (1 - anim.amplitude(1).period(0.4)(opacitySampler)) - 4, + }).generate() + if (opacitySampler < 1) { + window.requestAnimationFrame(step) + } else { + resolve() + } + } + window.requestAnimationFrame(step) + }) + } + + /** Start the logo hide animation. */ + animateHide(): Promise { + const self = this + const startTime = window.performance.now() + return new Promise(function (resolve) { + const step = (time: DOMHighResTimeStamp) => { + const opacitySampler = 1 - Math.min((time - startTime) / (1000 * 0.3), 1) + self.setOpacity(animation.easeInOutQuad(opacitySampler)) + if (opacitySampler > 0) { + window.requestAnimationFrame(step) + } else { + resolve() + } + } + window.requestAnimationFrame(step) + }) + } + + /** Start the spinning animation. */ + animateRotation() { + const indicator = this + let rotation = 0 + const step = (time: DOMHighResTimeStamp) => { + indicator.setRotation(rotation) + rotation = time / 6 + if (!indicator.destroyed) { + window.requestAnimationFrame(step) + } + } + window.requestAnimationFrame(step) + } +} + +// ============== +// === Loader === +// ============== + +/** The main loader class. It connects to the provided fetch responses and tracks their status. */ +export class Loader { + indicator: ProgressIndicator + totalBytes: number + receivedBytes: number + downloadSpeed: number + lastReceiveTime: number + initialized: Promise + capProgressAt: number + done: Promise + doneResolve: null | ((value: void | PromiseLike) => void) = null + constructor(cfg: Config) { + this.indicator = new ProgressIndicator(cfg) + this.totalBytes = 0 + this.receivedBytes = 0 + this.downloadSpeed = 0 + this.lastReceiveTime = performance.now() + this.initialized = this.indicator.initialized + this.capProgressAt = cfg.params.loaderDownloadToInitRatio.value + + this.done = new Promise(resolve => { + this.doneResolve = resolve + }) + } + + /** Load the provided resources. */ + load(resources: Response[]) { + const loaderError = (msg: string) => console.error(`Loader error. ${msg}`) + let missingContentLength = false + for (const resource of resources) { + const contentLength = resource.headers.get('content-length') + if (contentLength) { + this.totalBytes += parseInt(contentLength) + } else { + missingContentLength = true + } + const body = resource.clone().body + if (body) { + body.pipeTo(this.inputStream()).catch(err => logger.error(err)) + } else { + loaderError('Cannot read the response body.') + } + } + + if (missingContentLength || Number.isNaN(this.totalBytes)) { + loaderError("Server is not configured to send the 'Content-Length' metadata.") + this.totalBytes = 0 + } + } + + /** The current loading progress [0..1]. */ + value() { + if (this.totalBytes == 0) { + return 0 + } else { + return this.receivedBytes / this.totalBytes + } + } + + /** Check whether the loader finished downloading all assets. */ + isDone() { + return this.receivedBytes == this.totalBytes + } + + /** Run the hide animation and then remove the loader DOM element. */ + destroy() { + this.indicator.destroy() + } + + /** Callback run on every new received byte stream. */ + onReceive(newBytes: number) { + this.receivedBytes += newBytes + const time = performance.now() + const timeDiff = time - this.lastReceiveTime + if (timeDiff > 0) { + this.downloadSpeed = newBytes / timeDiff + this.lastReceiveTime = time + + const percent = this.showPercentageValue() + const speed = this.showDownloadSpeed() + const received = this.showReceivedBytes() + console.log(`${percent}% (${received}) (${speed}).`) + + const indicatorProgress = this.value() * this.capProgressAt + this.indicator.set(indicatorProgress) + } + if (this.isDone()) { + this.indicator.set(1) + if (this.doneResolve) { + this.doneResolve() + } + } + } + + /** Download percentage value. */ + showPercentageValue() { + return Math.round(100 * this.value()) + } + + /** Download total size value. */ + showTotalBytes() { + return `${math.formatMb(this.totalBytes)} MB` + } + + /** Download received bytes value. */ + showReceivedBytes() { + return `${math.formatMb(this.receivedBytes)} MB` + } + + /** Download speed value. */ + showDownloadSpeed() { + return `${math.formatMb(1000 * this.downloadSpeed)} MB/s` + } + + /** Internal function for attaching new fetch responses. */ + inputStream(): WritableStream { + const loader = this + return new WritableStream({ + write(t) { + loader.onReceive(t.length) + }, + }) + } +} diff --git a/lib/rust/ensogl/pack/js/src/runtime-libs/runtime-libs.ts b/lib/rust/ensogl/pack/js/src/runtime-libs/runtime-libs.ts new file mode 100644 index 0000000000..b75284cf2b --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/runtime-libs/runtime-libs.ts @@ -0,0 +1,5 @@ +// @ts-ignore +/* eslint @typescript-eslint/no-unsafe-return: "off" */ +export function spector() { + return require('spectorjs') +} diff --git a/lib/rust/ensogl/pack/js/src/shader-extractor/args.ts b/lib/rust/ensogl/pack/js/src/shader-extractor/args.ts new file mode 100644 index 0000000000..fa29f7f2d0 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/shader-extractor/args.ts @@ -0,0 +1,119 @@ +/** @file A simple argument parser. */ + +import * as util from 'node:util' + +// ========================== +// === Naming Conversions === +// ========================== + +/** Converts a camel case string to a kebab case string. */ +function camelToKebabCase(name: string) { + return name + .split('') + .map((letter, idx) => { + return letter.toUpperCase() === letter + ? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}` + : letter + }) + .join('') +} + +// ============== +// === Option === +// ============== + +class Option { + 'default': T | undefined + value: T | undefined + description: string + type: 'string' | 'boolean' + constructor(description: string, def?: T) { + this.default = def + this.description = description + if (def === true || def === false) { + this.type = 'boolean' + } else { + this.type = 'string' + } + } +} + +// ================= +// === ArgParser === +// ================= + +interface ParseArgsOptionConfig { + type: 'string' | 'boolean' + multiple?: boolean | undefined + short?: string | undefined + default?: string | boolean | string[] | boolean[] | undefined +} + +export class Args { + [key: string]: Option + help = new Option('Print help message.', false) + outDir = new Option( + 'The directory the extracted non-optimized shaders will be written to.' + ) +} + +export class ArgParser { + args = new Args() + + parse() { + const optionToFieldNameMap = new Map() + const options: Record = {} + for (const [fieldName, option] of Object.entries(this.args)) { + const optionName = camelToKebabCase(fieldName) + optionToFieldNameMap.set(optionName, fieldName) + options[optionName] = { type: option.type, default: option.default } + } + try { + const out = util.parseArgs({ options }) + for (const [optionName, optionValue] of Object.entries(out.values)) { + const fieldName = optionToFieldNameMap.get(optionName) + if (fieldName) { + // @ts-expect-error + this.args[fieldName].value = optionValue + } else { + console.error(`Unknown option: ${optionName}`) + process.exit(1) + } + } + } catch (error) { + const msg = error instanceof Error ? `${error.message}. ` : '' + console.error(`${msg}Use --help to learn about possible options.`) + process.exit(1) + } + if (this.args.help.value) { + this.printHelpAndExit(0) + } + } + + printHelp() { + console.log(`Options:`) + for (const [fieldName, option] of Object.entries(this.args)) { + const optionName = camelToKebabCase(fieldName) + let header = `--${optionName}` + if (option.type == 'string') { + const def = option.default != null ? `[${option.default}]` : '' + header += `=${def}` + } + console.log() + console.log(header) + console.log(option.description) + } + } + + printHelpAndExit(exitCode: number) { + this.printHelp() + process.exit(exitCode) + } +} + +/** Parse the command line arguments. */ +export function parse(): ArgParser { + const argParser = new ArgParser() + argParser.parse() + return argParser +} diff --git a/lib/rust/ensogl/pack/js/src/shader-extractor/fs.ts b/lib/rust/ensogl/pack/js/src/shader-extractor/fs.ts new file mode 100644 index 0000000000..14843a704b --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/shader-extractor/fs.ts @@ -0,0 +1,131 @@ +/** @file This module redefines some `node:fs` functions with embedded logging, so it is easy to + * track what they do. */ + +import { + MakeDirectoryOptions, + Mode, + ObjectEncodingOptions, + OpenMode, + PathLike, + RmDirOptions, + RmOptions, +} from 'node:fs' +import { FileHandle } from 'fs/promises' +import { Abortable } from 'node:events' +import { promises as fs } from 'fs' +import * as log from 'runner/log' +import { logger } from 'runner/log/logger' +import { Stream } from 'node:stream' + +// ================ +// === readFile === +// ================ + +export async function readFile( + path: PathLike | FileHandle, + options?: + | ({ + encoding?: null | undefined + flag?: OpenMode | undefined + } & Abortable) + | null +): Promise + +export async function readFile( + path: PathLike | FileHandle, + options: + | ({ + encoding: BufferEncoding + flag?: OpenMode | undefined + } & Abortable) + | BufferEncoding +): Promise + +/** Read a file and log the operation. */ +export async function readFile( + path: PathLike | FileHandle, + options?: + | (ObjectEncodingOptions & + Abortable & { + flag?: OpenMode | undefined + }) + | BufferEncoding + | null +): Promise { + return log.Task.asyncRun(`Reading file '${String(path)}'.`, async () => { + return await fs.readFile(path, options) + }) +} + +// ============== +// === unlink === +// ============== + +/** Unlink a file and log the operation. */ +export async function unlink(path: PathLike): Promise { + return log.Task.asyncRun(`Removing file '${String(path)}'.`, async () => { + return await fs.unlink(path) + }) +} + +// ============= +// === rmdir === +// ============= + +/** Remove a directory and log the operation. */ +export async function rmdir(path: PathLike, options?: RmDirOptions): Promise { + return log.Task.asyncRun(`Removing directory '${String(path)}'.`, async () => { + return await fs.rmdir(path, options) + }) +} + +// ============= +// === mkdir === +// ============= + +/** Make a directory and log the operation. */ +export async function mkdir( + path: PathLike, + options?: Mode | MakeDirectoryOptions | null +): Promise { + return log.Task.asyncRun(`Creating directory '${String(path)}'.`, async () => { + return await fs.mkdir(path, options) + }) +} + +// ========== +// === rm === +// ========== + +/** Remove a file or directory and log the operation. */ +export async function rm(path: PathLike, options?: RmOptions): Promise { + return log.Task.asyncRun(`Removing '${String(path)}'.`, async () => { + return await fs.rm(path, options) + }) +} + +// ================= +// === writeFile === +// ================= + +/** Write a file and log the operation. */ +export async function writeFile( + file: PathLike | FileHandle, + data: + | string + | NodeJS.ArrayBufferView + | Iterable + | AsyncIterable + | Stream, + options?: + | (ObjectEncodingOptions & { + mode?: Mode | undefined + flag?: OpenMode | undefined + } & Abortable) + | BufferEncoding + | null +): Promise { + return log.Task.asyncRun(`Writing file '${String(file)}'.`, async () => { + return await fs.writeFile(file, data, options) + }) +} diff --git a/lib/rust/ensogl/pack/js/src/shader-extractor/shader-extractor.ts b/lib/rust/ensogl/pack/js/src/shader-extractor/shader-extractor.ts new file mode 100644 index 0000000000..decdbfc798 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/shader-extractor/shader-extractor.ts @@ -0,0 +1,70 @@ +/** @file Tool for extracting shaders of EnsoGL shapes from compiled WASM binaries. */ + +import path from 'path' +import * as args from 'shader-extractor/args' +import * as fs from 'shader-extractor/fs' +import * as log from 'runner/log' +import * as name from 'runner/name' +import * as runner from 'runner/index' + +// =========== +// === App === +// =========== + +/** The main application. It loads the WASM file from disk, runs before main entry points, extract + * not optimized shaders and saves them to files. */ +class App extends runner.App { + override async loadWasm() { + const mainJsUrl = path.join(__dirname, this.config.params.pkgJsUrl.value) + const mainWasmUrl = path.join(__dirname, this.config.params.pkgWasmUrl.value) + const mainJs = await fs.readFile(mainJsUrl, 'utf8') + const mainWasm = await fs.readFile(mainWasmUrl) + this.wasm = await this.compileAndRunWasm(mainJs, mainWasm) + } + + async extractShaders(outDir: string) { + await log.Task.asyncRun('Extracting shaders code.', async () => { + const shadersMap = this.getShaders() + if (shadersMap) { + await log.Task.asyncRun(`Writing shaders to '${outDir}'.`, async () => { + await fs.rm(outDir, { recursive: true, force: true }) + await fs.mkdir(outDir) + const fileNames = [] + for (const [codePath, code] of shadersMap) { + const fileName = name.mangle(codePath) + const filePath = path.join(outDir, fileName) + await fs.writeFile(`${filePath}.vertex.glsl`, code.vertex) + await fs.writeFile(`${filePath}.fragment.glsl`, code.fragment) + fileNames.push(fileName) + } + const fileListPath = path.join(outDir, 'list.txt') + await fs.writeFile(fileListPath, fileNames.join('\n')) + }) + } + }) + } + + override async run(): Promise { + const parser = args.parse() + const outDir = parser.args.outDir.value + if (outDir) { + await log.Task.asyncRun('Running the program.', async () => { + app.config.print() + await app.loadAndInitWasm() + const r = app.runBeforeMainEntryPoints().then(() => { + return app.extractShaders(outDir) + }) + await r + }) + } else { + parser.printHelpAndExit(1) + } + } +} + +// ============ +// === Main === +// ============ + +const app = new App() +void app.run() diff --git a/lib/rust/ensogl/pack/js/src/wasm-pack-bundle/index.ts b/lib/rust/ensogl/pack/js/src/wasm-pack-bundle/index.ts new file mode 100644 index 0000000000..2388c964c4 --- /dev/null +++ b/lib/rust/ensogl/pack/js/src/wasm-pack-bundle/index.ts @@ -0,0 +1,5 @@ +import init from './pkg.js' +export * from './runtime-libs' +/* eslint @typescript-eslint/no-unsafe-assignment: "off" */ +/* eslint @typescript-eslint/no-unsafe-member-access: "off" */ +export { init } diff --git a/lib/rust/ensogl/pack/js/tsconfig.json b/lib/rust/ensogl/pack/js/tsconfig.json new file mode 100644 index 0000000000..b19a152ed3 --- /dev/null +++ b/lib/rust/ensogl/pack/js/tsconfig.json @@ -0,0 +1,29 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "rootDir": "src", + "baseUrl": "./src", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSourceMap": true, + "lib": ["ES2021", "dom"], + "module": "ESNext", + "moduleResolution": "node", + "noImplicitOverride": true, + "noUncheckedIndexedAccess": true, + "removeComments": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "target": "ES2019", + "skipLibCheck": true, + "outDir": "dist", + "declaration": true, + "declarationMap": true + }, + "ts-node": { + "esm": true + } +} diff --git a/lib/rust/ensogl/pack/src/lib.rs b/lib/rust/ensogl/pack/src/lib.rs new file mode 100644 index 0000000000..ba6e4b9104 --- /dev/null +++ b/lib/rust/ensogl/pack/src/lib.rs @@ -0,0 +1,531 @@ +//! EnsoGL Pack compiles Rust sources, precompile shaders of EnsoGL app, and outputs JS WASM loader, +//! additional JS runtime utilities, and a set of optimized shaders. It is a wrapper for `wasm-pack` +//! tool. +//! +//! # Compilation process. +//! When run, the following file tree will be created/used. The files/directories marked with '*' +//! are required to be included with your final application code. The files marked with '**' are +//! recommended to be included. +//! +//! ```text +//! workspace | The main workspace directory (repo root). +//! ├─ ... / this_crate | This crate's directory. +//! │ ╰─ js | This crate's JS sources. +//! │ ├─ runner | Runner of WASM app. Compiles to `dist/index.cjs`. +//! │ ├─ runtime-libs | Additional libs bundled with app. E.g. SpectorJS. +//! │ ├─ shader-extractor | App to extract shaders from WASM. +//! │ ╰─ wasm-pack-bundle | Glue for `wasm-pack` artifacts. +//! │ ╰─ index.ts | Copied to `target/ensogl-pack/wasm-pack/index.ts`. +//! ╰─ target | Directory where Rust and wasm-pack store build artifacts. +//! ╰─ ensogl-pack | Directory where ensogl-pack stores its build artifacts. +//! ├─ wasm-pack | Wasm-pack artifacts, re-created on every run. +//! │ ├─ pkg.js | Wasm-pack JS file to load WASM and glue it with snippets. +//! │ ├─ pkg_bg.wasm | Wasm-pack WASM bundle. +//! │ ├─ index.ts | Main file, copied from `this_crate/js/wasm-pack-bundle`. +//! │ ├─ runtime-libs.js | Bundled `this_crate/js/runtime-libs`. +//! │ ╰─ snippets | Rust-extracted JS snippets. +//! │ ╰─ .js | A single Rust-extracted JS snippet. +//! ├─ shaders | Not optimized shaders sources extracted from WASM bundle. +//! │ ├─ list.txt | List of extracted not optimized shaders (no extensions). +//! │ ├─ ..glsl | A single not optimized shader. (Stage = vertex|fragment). +//! │ ╰─ ... +//! ├─ shaders-hash | Not optimized shader hashes. Used to detect changes. +//! │ ├─ ..hash | A single not optimized shader hash. +//! │ ╰─ ... +//! ├─ runtime-libs +//! │ ╰─ runtime-libs.js +//! ╰─ dist | Final build artifacts of ensogl-pack. +//! * ├─ index.cjs | The main JS bundle to load WASM and JS wasm-pack bundles. +//! ├─ index.cjs.map | The sourcemap mapping to sources in TypeScript. +//! ** ├─ index.d.ts | TypeScript types interface file. +//! ├─ shader-extractor.cjs | Node program to extract non optimized shaders from WASM. +//! ├─ shader-extractor.cjs.map | The sourcemap mapping to sources in TypeScript. +//! ├─ shader-extractor.d.ts | TypeScript types interface file. +//! * ├─ pkg.js | The `pks.js` artifact of wasm-pack WITH bundled snippets. +//! ├─ pkg.js.map | The sourcemap mapping to `pkg.js` generated by wasm-pack. +//! * ├─ pkg.wasm | The `pks_bg.wasm` artifact of wasm-pack. +//! * ╰─ shaders | Optimized shaders that contain main function code only. +//! ├─ list.txt | List of optimized shaders (no extensions). +//! ├─ ..glsl | A single optimized shader. (Stage = vertex|fragment). +//! ╰─ ... +//! ``` +//! +//! The high-level app compilation process is summarized below: +//! +//! 1. If the `dist/index.cjs` file does not exist, or its modification date is older than +//! `this_crate/js` sources: +//! +//! 1. `npm run install` is run in the `this_crate/js` directory. +//! +//! 2. The `this_crate/js/runner` is compiled to `target/ensogl-pack/dist/index.cjs`. This is the +//! main file which is capable of loading WASM file, displaying a loading screen, running +//! before-main entry points, and running the main entry point of the application. +//! +//! 3. The `this_crate/js/shader-extractor` is compiled to +//! `target/ensogl-pack/dist/shader-extractor.cjs`. This is a node program that extracts +//! non-optimized shaders from the WASM file. +//! +//! 4. The `this_crate/js/runtime-libs` is compiled to +//! `target/ensogl-pack/runtime-libs/runtime-libs.js`. This is a bundle containing additional JS +//! libs, such as SpectorJS. +//! +//! 2. The rust sources are build with `wasm-pack`, which produces the following artifacts: +//! `target/ensogl-pack/wasm-pack/{pkg.js, pkg_bg.wasm, snippets}`. The file `pkg_bg.wasm` is copied +//! to `target/ensogl-pack/dist/pkg.wasm`. +//! +//! 3. The file `this_crate/js/wasm-pack-bundle/index.ts` is copied to +//! `target/ensogl-pack/wasm-pack/index.ts`. This is the main file which when compiled glues +//! `pkg.js`, `snippets`, and `runtime-libs.js` into a single bundle. +//! +//! 4. The program `target/ensogl-pack/dist/shader-extractor.cjs` is run. It loads +//! `target/dist/pkg.wasm` and writes non-optimized shader code to `target/ensogl-pack/shaders`. +//! +//! 5. For each shader, the hash of its code is computed and compared to the hash stored in +//! `target/ensogl-pack/shaders-hash`. If the hash did not exist or is different, the shader is +//! optimized by using `glslc`, spirv-opt`, and `spirv-cross`, and the result is written to +//! `dist/shaders`. +//! +//! 6. The `target/ensogl-pack/wasm-pack/index.ts` is compiled to +//! `target/ensogl-pack/dis/index.cjs`. It is then compiled to `target/ensogl-pack/dist/index.cjs`. +//! +//! +//! +//! # Runtime process. +//! When `target/dist/index.cjs` is run: +//! +//! 1. The following files are downloaded from a server: `target/dist/{pkg.js, pkg.wasm, shaders}`. +//! 2. The code from `pkg.js` is run to compile the WASM file. +//! 3. All before-main entry points are run. +//! 4. Optimized shaders are uploaded to the EnsoGL application. +//! 5. The main entry point is run. + +// === Features === +#![feature(async_closure)] +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] + +use ide_ci::prelude::*; + +use enso_prelude::calculate_hash; +use ide_ci::program::EMPTY_ARGS; +use ide_ci::programs::shaderc::Glslc; +use ide_ci::programs::shaderc::SpirvOpt; +use ide_ci::programs::spirv_cross::SpirvCross; +use ide_ci::programs::wasm_pack::WasmPackCommand; +use manifest_dir_macros::path; +use std::env; +use std::path::Path; +use std::path::PathBuf; +use walkdir::WalkDir; + + +// ============== +// === Export === +// ============== + +pub use ide_ci::prelude; + + + +// ================= +// === Hot Fixes === +// ================= + +/// A hot-fix for a bug on macOS, where `std::fs::copy` causes cargo-watch to loop infinitely. +/// See: https://github.com/watchexec/cargo-watch/issues/242 +pub fn copy(source_file: impl AsRef, destination_file: impl AsRef) -> Result { + if env::consts::OS == "macos" { + Command::new("cp").arg(source_file.as_ref()).arg(destination_file.as_ref()).spawn()?; + Ok(()) + } else { + ide_ci::fs::copy(source_file, destination_file) + } +} + + + +// ============= +// === Paths === +// ============= + +/// Paths of the directories and files used by `ensogl-pack`. This struct maps to variables the +/// directory layout described in the docs of this module. +#[derive(Debug, Default)] +#[allow(missing_docs)] +pub struct Paths { + pub workspace: PathBuf, + pub this_crate: paths::ThisCrate, + pub target: paths::Target, +} + +macro_rules! define_paths { + ($( + $name:ident { + $($field:ident : $field_ty:ty),* $(,)? + } + )*) => {$( + #[derive(Debug, Default)] + #[allow(missing_docs)] + pub struct $name { + pub root: PathBuf, + $( pub $field: $field_ty ),* + } + + impl Deref for $name { + type Target = PathBuf; + fn deref(&self) -> &Self::Target { + &self.root + } + } + + impl AsRef for $name { + fn as_ref(&self) -> &std::path::Path { + &self.root + } + } + + impl AsRef for $name { + fn as_ref(&self) -> &OsStr { + self.root.as_ref() + } + } + )*}; +} + +pub mod paths { + use super::*; + define_paths! { + ThisCrate { + js: ThisCrateJs, + } + + ThisCrateJs { + wasm_pack_bundle: ThisCrateJsWasmPackBundle, + } + + ThisCrateJsWasmPackBundle { + index: PathBuf + } + + Target { + ensogl_pack: TargetEnsoglPack, + } + + TargetEnsoglPack { + wasm_pack: TargetEnsoglPackWasmPack, + shaders: TargetEnsoglPackShaders, + shaders_hash: PathBuf, + runtime_libs: TargetEnsoglPackRuntimeLibs, + dist: TargetEnsoglPackDist, + } + + TargetEnsoglPackShaders { + list: PathBuf, + } + + TargetEnsoglPackRuntimeLibs { + runtime_libs: PathBuf, + } + + TargetEnsoglPackWasmPack { + index: PathBuf, + pkg_bg: PathBuf, + pkg_js: PathBuf, + runtime_libs: PathBuf, + } + + TargetEnsoglPackDist { + app: PathBuf, + shader_extractor: PathBuf, + pkg_js: PathBuf, + main_wasm: PathBuf, + shaders: TargetEnsoglPackDistShaders, + } + + TargetEnsoglPackDistShaders { + list: PathBuf, + } + } +} + +const WASM_PACK_OUT_NAME: &str = "pkg"; + +impl Paths { + pub async fn new() -> Result { + let mut p = Paths::default(); + let current_cargo_path = Path::new(path!("Cargo.toml")); + p.this_crate.root = current_cargo_path.try_parent()?.into(); + p.this_crate.js.root = p.this_crate.join("js"); + p.this_crate.js.wasm_pack_bundle.root = + p.this_crate.js.root.join("src").join("wasm-pack-bundle"); + p.this_crate.js.wasm_pack_bundle.index = p.this_crate.js.wasm_pack_bundle.join("index.ts"); + p.workspace = workspace_dir().await?; + p.target.root = p.workspace.join("target"); + p.target.ensogl_pack.root = p.target.join("ensogl-pack"); + p.target.ensogl_pack.wasm_pack.root = p.target.ensogl_pack.join("wasm-pack"); + let pkg_wasm = format!("{WASM_PACK_OUT_NAME}_bg.wasm"); + let pkg_js = format!("{WASM_PACK_OUT_NAME}.js"); + p.target.ensogl_pack.wasm_pack.index = p.target.ensogl_pack.wasm_pack.join("index.ts"); + p.target.ensogl_pack.wasm_pack.pkg_bg = p.target.ensogl_pack.wasm_pack.join(pkg_wasm); + p.target.ensogl_pack.wasm_pack.pkg_js = p.target.ensogl_pack.wasm_pack.join(pkg_js); + p.target.ensogl_pack.wasm_pack.runtime_libs = + p.target.ensogl_pack.wasm_pack.join("runtime-libs.js"); + p.target.ensogl_pack.shaders.root = p.target.ensogl_pack.join("shaders"); + p.target.ensogl_pack.shaders.list = p.target.ensogl_pack.shaders.join("list.txt"); + p.target.ensogl_pack.shaders_hash = p.target.ensogl_pack.join("shaders-hash"); + p.target.ensogl_pack.runtime_libs.root = p.target.ensogl_pack.join("runtime-libs"); + p.target.ensogl_pack.runtime_libs.runtime_libs = + p.target.ensogl_pack.runtime_libs.join("runtime-libs.js"); + p.target.ensogl_pack.dist.root = p.target.ensogl_pack.join("dist"); + p.target.ensogl_pack.dist.app = p.target.ensogl_pack.dist.join("index.cjs"); + p.target.ensogl_pack.dist.shader_extractor = + p.target.ensogl_pack.dist.join("shader-extractor.cjs"); + p.target.ensogl_pack.dist.pkg_js = p.target.ensogl_pack.dist.join("pkg.js"); + p.target.ensogl_pack.dist.main_wasm = p.target.ensogl_pack.dist.join("pkg.wasm"); + p.target.ensogl_pack.dist.shaders.root = p.target.ensogl_pack.dist.join("shaders"); + p.target.ensogl_pack.dist.shaders.list = p.target.ensogl_pack.dist.shaders.join("list.txt"); + Ok(p) + } +} + +pub async fn workspace_dir() -> Result { + let output = Command::new(env!("CARGO")) + .arg("locate-project") + .arg("--workspace") + .arg("--message-format=plain") + .output_ok() + .await? + .stdout; + let cargo_path = Path::new(std::str::from_utf8(&output)?.trim()); + Ok(cargo_path.try_parent()?.to_owned()) +} + + +// ============= +// === Build === +// ============= + +/// The arguments to `wasm-pack build` that `ensogl-pack` wants to customize. +pub struct WasmPackOutputs { + /// Value to passed as `--out-dir` to `wasm-pack`. + pub out_dir: PathBuf, + /// Value to passed as `--out-name` to `wasm-pack`. + pub out_name: String, +} + +/// Check the modification time of all files in this crate's `js` directory and compare them with +/// the modification time of dist artifacts, if any. Do not traverse `node_modules` directory. +fn check_if_ts_needs_rebuild(paths: &Paths) -> Result { + let walk = WalkDir::new(&paths.this_crate.js).into_iter(); + let walk_no_node_modules = walk.filter_entry(|e| e.file_name() != "node_modules"); + let mut newest_mod_time: Option = None; + for opt_entry in walk_no_node_modules { + let entry = opt_entry?; + if entry.file_type().is_file() { + let metadata = entry.metadata()?; + let mod_time = metadata.modified()?; + newest_mod_time = Some(newest_mod_time.map_or(mod_time, |t| t.max(mod_time))); + } + } + if let Ok(app_js_metadata) = std::fs::metadata(&paths.target.ensogl_pack.dist.app) { + let app_js_mod_time = app_js_metadata.modified()?; + Ok(newest_mod_time.map_or(true, |t| t > app_js_mod_time)) + } else { + Ok(true) + } +} + +/// Compile TypeScript sources of this crate in case they were not compiled yet. +async fn compile_this_crate_ts_sources(paths: &Paths) -> Result<()> { + println!("compile_this_crate_ts_sources"); + if check_if_ts_needs_rebuild(paths)? { + info!("EnsoGL Pack TypeScript sources changed, recompiling."); + ide_ci::programs::Npm.cmd()?.install().current_dir(&paths.this_crate.js).run_ok().await?; + let run_script = async move |script_name, script_args: &[&str]| { + ide_ci::programs::Npm + .cmd()? + .run(script_name, script_args) + .current_dir(&paths.this_crate.js) + .run_ok() + .await + }; + + info!("Linting TypeScript sources."); + run_script("lint", &EMPTY_ARGS).await?; + + info!("Building TypeScript sources."); + let args = ["--", &format!("--out-dir={}", paths.target.ensogl_pack.dist.display())]; + run_script("build", &args).await?; + let args = ["--", &format!("--out-dir={}", paths.target.ensogl_pack.dist.display())]; + run_script("build-shader-extractor", &args).await?; + println!("BUILD build-runtime-libs"); + let args = ["--", &format!("--outdir={}", paths.target.ensogl_pack.runtime_libs.display())]; + run_script("build-runtime-libs", &args).await?; + } else { + println!("NO BUILD"); + } + Ok(()) +} + +/// Run wasm-pack to build the wasm artifact. +pub async fn run_wasm_pack( + paths: &Paths, + provider: impl FnOnce(WasmPackOutputs) -> Result, +) -> Result<()> { + info!("Obtaining and running the wasm-pack command."); + let replaced_args = WasmPackOutputs { + out_dir: paths.target.ensogl_pack.wasm_pack.root.clone(), + out_name: WASM_PACK_OUT_NAME.to_string(), + }; + let mut command = provider(replaced_args).context("Failed to obtain wasm-pack command.")?; + command.run_ok().await?; + + copy(&paths.this_crate.js.wasm_pack_bundle.index, &paths.target.ensogl_pack.wasm_pack.index)?; + copy( + &paths.target.ensogl_pack.runtime_libs.runtime_libs, + &paths.target.ensogl_pack.wasm_pack.runtime_libs, + )?; + + compile_wasm_pack_artifacts( + &paths.target.ensogl_pack.wasm_pack, + &paths.target.ensogl_pack.wasm_pack.index, + &paths.target.ensogl_pack.dist.pkg_js, + ) + .await?; + ide_ci::fs::copy( + &paths.target.ensogl_pack.wasm_pack.pkg_bg, + &paths.target.ensogl_pack.dist.main_wasm, + ) +} + +/// Compile wasm-pack artifacts (JS sources and snippets) to a single bundle. +async fn compile_wasm_pack_artifacts(pwd: &Path, pkg_js: &Path, out: &Path) -> Result { + info!("Compiling {}.", pkg_js.display()); + ide_ci::programs::Npx + .cmd()? + .args([ + "--yes", + "esbuild", + pkg_js.display().to_string().as_str(), + "--format=cjs", + "--bundle", + "--sourcemap", + "--platform=node", + &format!("--outfile={}", out.display()), + ]) + .current_dir(pwd) + .run_ok() + .await +} + +/// Extract non-optimized shaders from the WASM artifact. +async fn extract_shaders(paths: &Paths) -> Result<()> { + info!("Extracting shaders from generated WASM file."); + ide_ci::programs::Node + .cmd()? + .arg(&paths.target.ensogl_pack.dist.shader_extractor) + .arg("--out-dir") + .arg(&paths.target.ensogl_pack.shaders) + .run_ok() + .await +} + +/// Optimize the extracted shaders by using `glslc`, `spirv-opt` and `spirv-cross`. +async fn optimize_shaders(paths: &Paths) -> Result<()> { + info!("Optimizing extracted shaders."); + ide_ci::fs::create_dir_if_missing(&paths.target.ensogl_pack.dist.shaders)?; + + let stages = ["vertex", "fragment"]; + let shaders_list = ide_ci::fs::read_to_string(&paths.target.ensogl_pack.shaders.list)?; + let shaders_prefixes: Vec<_> = shaders_list.lines().collect(); + for shader_prefix in shaders_prefixes { + info!("Optimizing '{shader_prefix}'."); + for stage in stages { + let base_path = paths.target.ensogl_pack.shaders.join(shader_prefix); + let base_path = base_path.display(); + let stage_path = format!("{base_path}.{stage}"); + let glsl_path = stage_path.with_appended_extension("glsl"); + let spv_path = stage_path.with_appended_extension("spv"); + let spv_opt_path = stage_path.with_appended_extension("opt.spv"); + let glsl_opt_path = stage_path.with_appended_extension("opt.glsl"); + let glsl_file_name = format!("{shader_prefix}.{stage}.glsl"); + let hash_file_name = format!("{shader_prefix}.{stage}.hash"); + let glsl_opt_dist_path = paths.target.ensogl_pack.dist.shaders.join(&glsl_file_name); + let hash_path = paths.target.ensogl_pack.shaders_hash.join(&hash_file_name); + let content = ide_ci::fs::read_to_string(&glsl_path)?; + let old_hash = ide_ci::fs::read_to_string(&hash_path).ok(); + let hash = calculate_hash(&content).to_string(); + if let Some(old_hash) = old_hash { + if old_hash == hash { + info!("Skipping '{shader_prefix}.{stage}' because it has not changed."); + continue; + } + } + ide_ci::fs::write(&hash_path, hash)?; + + let spv_path = spv_path.as_str(); + let glsl_path = glsl_path.as_str(); + let shader_stage = &format!("-fshader-stage={stage}"); + let glslc_args = ["--target-env=opengl", shader_stage, "-o", spv_path, glsl_path]; + let spirv_opt_args = ["-O", "-o", spv_opt_path.as_str(), spv_path.as_str()]; + let spirv_cross_args = ["--output", glsl_opt_path.as_str(), spv_opt_path.as_str()]; + Glslc.cmd()?.args(glslc_args).run_ok().await?; + SpirvOpt.cmd()?.args(spirv_opt_args).run_ok().await?; + SpirvCross.cmd()?.args(spirv_cross_args).run_ok().await?; + + let content = ide_ci::fs::read_to_string(&glsl_opt_path)?.replace("\r\n", "\n"); + let extract_err = || format!("Failed to process shader '{}'.", glsl_opt_path.as_str()); + let code = extract_main_shader_code(&content).with_context(extract_err)?; + ide_ci::fs::write(&glsl_opt_dist_path, code)?; + } + } + ide_ci::fs::write(&paths.target.ensogl_pack.dist.shaders.list, &shaders_list) +} + +/// Read the optimized shader code, extract the main function body and preserve all top-level +/// variable declarations. +fn extract_main_shader_code(code: &str) -> Result { + let main_start_str = "void main()\n{"; + let main_end_str = "}"; + let main_fn_find_err = "Failed to find main function."; + let main_start = code.find(main_start_str).with_context(|| main_fn_find_err)?; + let main_end = code.rfind(main_end_str).with_context(|| main_fn_find_err)?; + let before_main = &code[..main_start]; + let declarations: Vec<&str> = before_main + .lines() + .filter_map(|line| { + let version_def = line.starts_with("#version "); + let precision_def = line.starts_with("precision "); + let layout_def = line.starts_with("layout("); + let def = version_def || precision_def || layout_def; + (!def).then_some(line) + }) + .collect(); + let declarations = declarations.join("\n"); + let main_content = &code[main_start + main_start_str.len()..main_end]; + Ok(format!("{}\n{}", declarations, main_content)) +} + +/// Wrapper over `wasm-pack build` command. +/// +/// # Arguments +/// * `outputs` - The outputs that'd be usually given to `wasm-pack build` command. +/// * `provider` - Function that generates an invocation of the `wasm-pack build` command that has +/// applied given (customized) output-related arguments. +pub async fn build( + outputs: WasmPackOutputs, + provider: impl FnOnce(WasmPackOutputs) -> Result, +) -> Result { + // FIXME: [mwu] To be removed, when shader tools are properly handled as a goodie-thingy. + let _ = ide_ci::env::prepend_to_path(r"C:\varia\install\bin"); + let paths = Paths::new().await?; + compile_this_crate_ts_sources(&paths).await?; + run_wasm_pack(&paths, provider).await?; + extract_shaders(&paths).await?; + optimize_shaders(&paths).await?; + let out_dir = Path::new(&outputs.out_dir); + ide_ci::fs::copy(&paths.target.ensogl_pack.dist, out_dir) +} diff --git a/lib/rust/ensogl/src/lib.rs b/lib/rust/ensogl/src/lib.rs index fb36636d0c..ca72c5e57a 100644 --- a/lib/rust/ensogl/src/lib.rs +++ b/lib/rust/ensogl/src/lib.rs @@ -1,5 +1,101 @@ //! EnsoGL is a blazing fast vector rendering engine. To learn more about its features and //! architecture design, read the [`README.md`]. +//! +//! +//! +//! # Debug Display Modes +//! Development of visual components is hard, especially if they react to asynchronous events or are +//! animated. Thus, EnsoGL provides you with multiple Debug Display Modes, allowing you to see the +//! scene from different perspectives. The modes can be switched by using ctrl + alt + +//! 0-9 shortcuts. +//! +//! +//! ## The Default Mode (ctrl + alt + 0) +//! This is the resulting picture that you see by default. For the purpose of this documentation, we +//! are using the "Focus Management" demo scene: +//! +//! https://user-images.githubusercontent.com/1623053/203922752-70687520-11c1-4808-b4fc-519ab273a755.mov +//! +//! +//! ## The Default Sprite UV Mode (ctrl + alt + 1) +//! This mode shows full size of sprites with their UV information encoded mapped to RG channels. +//! Please note that in case of shapes, UV coordinates may exceed the [0, 1] range in order to +//! improve shapes anti-aliasing. The UV space outside the [0, 1] range has a blue tint applied: +//! +//! https://user-images.githubusercontent.com/1623053/203923358-f6785ae8-e958-44c1-8403-0871a430e581.mov +//! +//! +//! ## The Debug Sprite Overview Mode (ctrl + alt + 2) +//! In this mode all shapes are colored based on their ID-encoding with anti-aliasing disabled. +//! +//! By moving the mouse vertically, you can change the border size around sprites. It allows +//! discovering small, fully transparent, or degenerated sprites easily. By moving the mouse +//! horizontally, you can change the sprite opacity, which allows discovering sprites hidden behind +//! other sprites. +//! +//! By clicking the mouse, you can change the color seed for the ID-encoding. In this mode, +//! mouse interactions with the scene (e.g. clicking) are disabled. +//! +//! https://user-images.githubusercontent.com/1623053/203924084-cb387fa8-bb18-47c9-adf5-7fc5e022432d.mov +//! +//! ## The Debug Sprite Grid Mode (ctrl + alt + 3) +//! In this mode, a pixel-perfect grid is displayed over shapes colored based on their ID-encoding. +//! +//! The grid is displayed for each sprite using the sprite's local coordinate system. When zooming, +//! the grid density is refined. With the maximum zoom, the grid elements are equal to one virtual +//! pixel on the screen (on high-density screens, such as Retina, the grid elements are equal to +//! several physical pixels). +//! +//! By clicking the mouse, you can change the color seed for the ID-encoding. In this mode, mouse +//! interactions with the scene (e.g. clicking) are disabled. +//! +//! Please note, that rendering artifacts can be observed when zooming in/out the grid (lines can +//! "flicker"). This is because the grid view is implemented using a very simple, non-antialiased +//! equation that does not use EnsoGL's shape system. This is done on purpose. Bugs in the shape +//! renderer should not cause the pixel grid to be rendered incorrectly. +//! +//! https://user-images.githubusercontent.com/1623053/203925350-e741f5f1-c099-4a8e-a0ae-b739c78fc7c6.mov +//! +//! +//! ## The Debug SDF Mode (ctrl + alt + 4) +//! In this mode, all shapes are rendered as SDF equation iso-lines. They represent the distance +//! from the shape border. They also visualize what will happen to the shape after it is shrinked or +//! expanded. +//! +//! In most cases, expanded shapes have more rounded corners, while shrinked shapes, less rounded +//! ones. +//! +//! https://user-images.githubusercontent.com/1623053/203926029-40f554ed-2418-4a16-97f8-4102beb155cc.mov +//! +//! +//! ## The Debug Shape AA Span Mode (ctrl + alt + 5) +//! In order to properly display a shape close to the borders of its canvas, the underlying sprite +//! size needs to be increased. For example, when displaying a rectangle, after moving the shape 0.5 +//! to the right, and slightly zooming the scene, some pixels on the left and right of the rectangle +//! should be anti-aliased. As the anti-aliasing is performed in the fragment shader, the sprite +//! size needs to be increased to cover this additional anti-aliased area. In this mode this +//! additional anti-aliased area is displayed in red. Please note that the area size changes +//! depending on the zoom level. +//! +//! https://user-images.githubusercontent.com/1623053/203926879-255f401e-ff52-4ae6-8cca-883b16c8e39b.mov +//! +//! +//! ## The Debug Instance ID Mode (ctrl + alt + 6) +//! Each shape instance has a separate ID which is used to identify the shape after a mouse +//! interaction occurs (such as mouse hover, or mouse click). In this mode, all shapes are colored +//! based on their ID-encoding with anti-aliasing disabled. +//! +//! By clicking the mouse, you can change the color seed for the ID-encoding. In this mode, +//! mouse interactions with the scene (e.g. clicking) are disabled. +//! +//! https://user-images.githubusercontent.com/1623053/203927187-33ee6a81-6251-4dfe-8238-4181ebd289fa.mov +//! +//! +//! ## Invalid Debug Display Modes (ctrl + alt + 7-9) +//! These modes do not exist yet. If they are selected, a checkerboard pattern will be displayed +//! instead: +//! +//! https://user-images.githubusercontent.com/1623053/203927501-31b73b56-414e-40d2-aec0-cb90dad6a908.mov // === Standard Linter Configuration === #![deny(non_ascii_idents)] diff --git a/lib/rust/prelude/src/hash.rs b/lib/rust/prelude/src/hash.rs new file mode 100644 index 0000000000..1bb4d1420a --- /dev/null +++ b/lib/rust/prelude/src/hash.rs @@ -0,0 +1,18 @@ +//! Hashing utilities. + +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; + + + +// ===================== +// === Hashing Utils === +// ===================== + +/// Calculate the hash of the provided value using the `DefaultHasher`. +pub fn calculate_hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() +} diff --git a/lib/rust/prelude/src/lib.rs b/lib/rust/prelude/src/lib.rs index 38c078e312..e9dcc529a4 100644 --- a/lib/rust/prelude/src/lib.rs +++ b/lib/rust/prelude/src/lib.rs @@ -32,6 +32,7 @@ pub mod debug; pub mod env; mod fail; pub mod future; +mod hash; mod leak; mod macros; mod not_same; @@ -58,9 +59,12 @@ pub use anyhow; pub use collections::*; pub use data::*; pub use debug::*; +pub use enso_shapely::before_main; pub use enso_shapely::clone_ref::*; pub use enso_shapely::impl_clone_ref_as_clone; +pub use enso_shapely::root_call_path; pub use fail::*; +pub use hash::*; pub use leak::Leak; pub use leak::*; pub use macros::*; @@ -103,6 +107,11 @@ pub use std::collections::hash_map::DefaultHasher; pub use std::hash::Hash; pub use std::hash::Hasher; +/// Re-export of [`wasm_bindgen`] is needed because the code generated by [`before_main`] macro +/// uses it. Unfortunately, `$crate` does not exist in proc macros, so it is not possible to +/// refer to [`wasm_bindgen`] from the macro in a generic way. +pub use wasm_bindgen; + pub use enso_reflect::prelude::*; pub use std::ops::AddAssign; diff --git a/lib/rust/shapely/macros/src/before_main.rs b/lib/rust/shapely/macros/src/before_main.rs new file mode 100644 index 0000000000..e0c3745ce3 --- /dev/null +++ b/lib/rust/shapely/macros/src/before_main.rs @@ -0,0 +1,66 @@ +//! A macro allowing running functions after WASM initialization, before the main function. + +use crate::prelude::*; + +use crate::root_call_path::root_call_path; + + + +// ======================== +// === Main entry point === +// ======================== + +/// Mangle the name replacing `_` with `__` and all non-ASCII characters with `__`. The +/// JS code contains a counterpart function that allows un-mangling the files, thus, after changing +/// this code, the JS one has to be updated as well. +fn mangle_name(name: &str) -> String { + name.chars() + .map(|c| if c.is_ascii_alphanumeric() { c.to_string() } else { format!("_{}_", c as u32) }) + .collect() +} + +/// Functions exposed in WASM have to have unique names. This utility creates a name based on the +/// location the function was defined at (module path, line number, column number). +fn unique_name() -> String { + mangle_name(&root_call_path()) +} + +/// The prefix of the before-main entry point function in WASM. The JS code contains a code +/// referring to that name as well, so if you change it, you have to update the JS code as well. +const BEFORE_MAIN_ENTRY_POINT_PREFIX: &str = "before_main_entry_point"; + +/// The default priority of the before-main entry point. This number will be used as part of the +/// function name. Before-main entry points are sorted by name before being run. +const DEFAULT_PRIORITY: usize = 100; + +/// Convert the function to a before-main entry point. Please note that the [`wasm-bindgen`] macro +/// has to be in scope for the result to compile. +pub fn run( + args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let mut args_iter = args.into_iter(); + let priority = match args_iter.next() { + None => DEFAULT_PRIORITY, + Some(token) => { + if args_iter.next().is_some() { + panic!( + "Expected maximum one argument, the entry point priority. If missing, the \ + default priority will be used ({DEFAULT_PRIORITY})." + ); + } + match token.to_string().parse::() { + Ok(priority) => priority, + Err(_) => panic!("The priority must be a number."), + } + } + }; + let mut input_fn = syn::parse_macro_input!(input as syn::ImplItemMethod); + let name = format!("{BEFORE_MAIN_ENTRY_POINT_PREFIX}_{priority}_{}", unique_name()); + input_fn.sig.ident = quote::format_ident!("{name}"); + let output = quote! { + #[wasm_bindgen::prelude::wasm_bindgen] + #input_fn + }; + output.into() +} diff --git a/lib/rust/shapely/macros/src/derive_entry_point.rs b/lib/rust/shapely/macros/src/derive_entry_point.rs index 8f251b236e..2228cfc68c 100644 --- a/lib/rust/shapely/macros/src/derive_entry_point.rs +++ b/lib/rust/shapely/macros/src/derive_entry_point.rs @@ -38,9 +38,21 @@ pub fn derive( }; let fn_name_str = base_name_to_fn_name(&fn_name_str); let fn_name = quote::format_ident!("{}", fn_name_str); + let docs_fn_name = quote::format_ident!("docs_of_{}", fn_name_str); let decl = syn::parse_macro_input!(input as syn::Item); match decl { syn::Item::Fn(f) => { + let mut docs = String::new(); + for attr in &f.attrs { + if attr.path.is_ident("doc") { + for token in attr.tokens.clone() { + if let proc_macro2::TokenTree::Literal(lit) = token { + docs.push_str(lit.to_string().trim_matches('"')); + } + } + } + } + docs = docs.trim().to_string(); let name = f.sig.ident.to_string(); if &name != "main" { panic!("The function should be named 'main'."); @@ -51,11 +63,17 @@ pub fn derive( let block = &f.block; let output = quote! { #(#attrs)* - #[wasm_bindgen] + #[wasm_bindgen::prelude::wasm_bindgen] pub #fn_sig { init_global(); #block } + + /// Docs for the entry point, exposed to WASM. + #[wasm_bindgen::prelude::wasm_bindgen] + pub fn #docs_fn_name() -> String { + #docs.to_string() + } }; output.into() } diff --git a/lib/rust/shapely/macros/src/lib.rs b/lib/rust/shapely/macros/src/lib.rs index 721aeb83ea..802920c08b 100644 --- a/lib/rust/shapely/macros/src/lib.rs +++ b/lib/rust/shapely/macros/src/lib.rs @@ -4,6 +4,8 @@ // === Features === #![feature(exact_size_is_empty)] +#![feature(proc_macro_span)] +#![feature(proc_macro_def_site)] // === Standard Linter Configuration === #![deny(non_ascii_idents)] #![warn(unsafe_code)] @@ -22,6 +24,7 @@ extern crate proc_macro; +mod before_main; mod derive_clone_ref; mod derive_entry_point; mod derive_for_each_variant; @@ -29,6 +32,7 @@ mod derive_iterator; mod derive_no_clone; mod gen; mod overlappable; +mod root_call_path; mod tagged_enum; mod prelude { @@ -310,3 +314,29 @@ pub fn gen( ) -> proc_macro::TokenStream { gen::run(attr, input) } + + +/// A macro allowing running functions after WASM initialization, before the main function. In order +/// to run a function before main, simply use this attribute (please note that the function has to +/// be public, as otherwise it can't be exported to WASM): +/// +/// ```text +/// #[before_main] +/// pub fn any_name { +/// println!("I'm running before main!"); +/// } +/// ``` +#[proc_macro_attribute] +pub fn before_main( + attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + before_main::run(attr, input) +} + +/// Macro reporting the root call path of itself. If it was used inside another macro "A", the +/// reported path will be the place where "A" was called. +#[proc_macro] +pub fn root_call_path(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + root_call_path::run(input) +} diff --git a/lib/rust/shapely/macros/src/root_call_path.rs b/lib/rust/shapely/macros/src/root_call_path.rs new file mode 100644 index 0000000000..6a119db4d2 --- /dev/null +++ b/lib/rust/shapely/macros/src/root_call_path.rs @@ -0,0 +1,32 @@ +//! Macro reporting the root call path of itself. If it was used inside another macro "A", the +//! reported path will be the place where "A" was called. + +use crate::prelude::*; + + + +// ======================== +// === Main entry point === +// ======================== + +/// Get the root call path of the call side at compile time. +pub fn root_call_path() -> String { + let mut span = proc_macro::Span::call_site(); + while let Some(parent) = span.parent() { + span = parent; + } + let source = span.source_file(); + let start = span.start(); + let path = source.path().to_str().unwrap_or_default().to_string(); + format!("{path}:{}:{}", start.line, start.column) +} + +/// Macro reporting the root call path of itself. If it was used inside another macro "A", the +/// reported path will be the place where "A" was called. +pub fn run(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let path = root_call_path(); + let output = quote! { + #path + }; + output.into() +} diff --git a/lib/rust/shapely/src/lib.rs b/lib/rust/shapely/src/lib.rs index c58c3249da..a0d873cee6 100644 --- a/lib/rust/shapely/src/lib.rs +++ b/lib/rust/shapely/src/lib.rs @@ -31,8 +31,6 @@ pub mod singleton; pub use enso_shapely_macros::*; pub use generator::GeneratingIterator; - - /// A macro which passes its input to its output. #[macro_export] macro_rules! identity { diff --git a/lib/rust/web/js/clipboard.js b/lib/rust/web/js/clipboard.js index e1868be625..3376f9025c 100644 --- a/lib/rust/web/js/clipboard.js +++ b/lib/rust/web/js/clipboard.js @@ -77,9 +77,13 @@ export function writeText(text) { /// [MSDN compatibility note](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText). let lastPaste = '' function init_firefox_fallback() { - window.addEventListener('paste', event => { - lastPaste = (event.clipboardData || window.clipboardData).getData('text') - }) + // Checking whether the window is defined. It could not be defined if the program is run in + // node, for example to extract the shaders. + if (typeof window !== 'undefined') { + window.addEventListener('paste', event => { + lastPaste = (event.clipboardData || window.clipboardData).getData('text') + }) + } } export function readText(callback) { diff --git a/lib/rust/web/src/binding/mock.rs b/lib/rust/web/src/binding/mock.rs index 7bad40bca6..df90b55f03 100644 --- a/lib/rust/web/src/binding/mock.rs +++ b/lib/rust/web/src/binding/mock.rs @@ -66,6 +66,7 @@ auto_impl_mock_default!(bool, i16, i32, u32, f64, String); pub trait MockData {} /// Macro used to generate mock structures. See the expansion of generated structures to learn more. +#[macro_export] macro_rules! mock_struct { ( $([$opt:ident])? $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? $(=> $deref:ident)? @@ -129,9 +130,12 @@ macro_rules! mock_struct { mock_struct_deref! {[$($deref)?] $name $(<$( $param $(:?$param_tp)?),*>)?} mock_struct_as_ref! {[$($opt)?] $name $(<$( $param $(:?$param_tp)?),*>)? $(=> $deref)?} + mock_struct_into_js_ref! {$name $(<$( $param $(:?$param_tp)?),*>)? $(=> $deref)?} }; } +/// Helper of [`mock_struct`]. +#[macro_export] macro_rules! mock_struct_as_ref { ([NO_AS_REF] $($ts:tt)*) => {}; ([] $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? @@ -149,6 +153,23 @@ macro_rules! mock_struct_as_ref { }; } +/// Helper of [`mock_struct`]. +#[macro_export] +macro_rules! mock_struct_into_js_ref { + (JsValue $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? $(=> $deref:ident)?) => {}; + ($name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? => JsValue) => {}; + ($name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? $(=> $deref:ident)?) => { + impl $(<$($param $(:?$param_tp)?),*>)? + From<$name $(<$($param),*>)?> for JsValue { + fn from(_: $name $(<$($param),*>)?) -> Self { + default() + } + } + }; +} + +/// Helper of [`mock_struct`]. +#[macro_export] macro_rules! mock_struct_deref { ([] $($ts:tt)*) => {}; ([$deref:ident] $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)?) => { @@ -224,7 +245,7 @@ macro_rules! mock_fn_gen { } /// Macro used to print the final version of the function. -#[macro_export] +#[macro_export(local_inner_macros)] macro_rules! mock_fn_gen_print { ([$($viz:ident)?] $name:ident $(<$($fn_tp:ident),*>)? ( $($args:tt)* ) $(-> $out:ty)? {$($body:tt)*} ) => { @@ -239,6 +260,7 @@ macro_rules! mock_fn_gen_print { } /// Combination of [`mock_struct`] and [`mock_pub_fn`]. +#[macro_export(local_inner_macros)] macro_rules! mock_data { ( $([$opt:ident])? $name:ident $(<$( $param:ident $(: ?$param_tp:ident)? ),*>)? $(=> $deref:ident)? @@ -328,18 +350,45 @@ impl JsValue { pub const NULL: JsValue = JsValue {}; } -/// All JS types can be converted to `JsValue` and thus it implements a generic conversion trait. -auto trait IsNotJsValue {} -impl !IsNotJsValue for JsValue {} -impl From for JsValue { - default fn from(_: A) -> Self { +impl AsRef for wasm_bindgen::JsValue { + fn as_ref(&self) -> &JsValue { + &JsValue::NULL + } +} + +impl From for JsValue { + fn from(_: wasm_bindgen::JsValue) -> Self { default() } } -impl AsRef for wasm_bindgen::JsValue { - fn as_ref(&self) -> &JsValue { - &JsValue::NULL +impl From for JsValue { + fn from(_: js_sys::Uint8Array) -> Self { + default() + } +} + +impl From for JsValue { + fn from(_: f32) -> Self { + default() + } +} + +impl From<&str> for JsValue { + fn from(_: &str) -> Self { + default() + } +} + +impl From<&String> for JsValue { + fn from(_: &String) -> Self { + default() + } +} + +impl From for JsValue { + fn from(_: String) -> Self { + default() } } @@ -379,6 +428,12 @@ impl AsRef for Closure { } } +/// This impl is provided to mimic the behavior of the [`wasm_bindgen::Closure`] type. It silences +/// clippy warnings. +impl Drop for Closure { + fn drop(&mut self) {} +} + // ================ @@ -406,6 +461,7 @@ impl From<&JsString> for String { // === Array === mock_data! { Array => Object fn length(&self) -> u32; + fn get(&self, index: u32) -> JsValue; fn of2(a: &JsValue, b: &JsValue) -> Array; fn of3(a: &JsValue, b: &JsValue, c: &JsValue) -> Array; fn of4(a: &JsValue, b: &JsValue, c: &JsValue, d: &JsValue) -> Array; @@ -413,16 +469,18 @@ mock_data! { Array => Object } -// === Error === - -mock_data! { Error => Object - fn new(message: &str) -> Self; +// === Map === +mock_data! { Map => Object + fn new() -> Self; + fn get(&self, key: &JsValue) -> JsValue; + fn set(&self, key: &JsValue, value: &JsValue) -> Map; + fn entries(&self) -> Iterator; } -impl From for JsValue { - fn from(_: Error) -> Self { - mock_default() - } + +// === Error === +mock_data! { Error => Object + fn new(message: &str) -> Self; } @@ -744,3 +802,42 @@ pub static window: Window = Window {}; #[allow(non_upper_case_globals)] #[allow(missing_docs)] pub static document: Document = Document::const_new(); + + + +// ================ +// === Iterator === +// ================ + +#[derive(Default, Clone, Copy, Debug)] +#[allow(missing_docs)] +pub struct Iterator; +impl MockDefault for Iterator { + fn mock_default() -> Self { + default() + } +} + +#[derive(Default, Clone, Copy, Debug)] +#[allow(missing_docs)] +pub struct IntoIter; +impl MockDefault for IntoIter { + fn mock_default() -> Self { + default() + } +} + +impl IntoIterator for Iterator { + type Item = Result; + type IntoIter = IntoIter; + fn into_iter(self) -> IntoIter { + default() + } +} + +impl std::iter::Iterator for IntoIter { + type Item = Result; + fn next(&mut self) -> Option { + None + } +} diff --git a/lib/rust/web/src/binding/wasm.rs b/lib/rust/web/src/binding/wasm.rs index a039869cb8..7c115705de 100644 --- a/lib/rust/web/src/binding/wasm.rs +++ b/lib/rust/web/src/binding/wasm.rs @@ -11,6 +11,7 @@ pub use js_sys::Array; pub use js_sys::Error; pub use js_sys::Function; pub use js_sys::JsString; +pub use js_sys::Map; pub use js_sys::Object; pub use std::time::Duration; pub use std::time::Instant; @@ -121,8 +122,19 @@ macro_rules! wasm_lazy_global { }; } +/// Get the global window object. +/// +/// # Safety +/// We are using an unsafe cast here in order to make it working in node. Please note that node does +/// NOT expose a `window` global object. We are creating it there manually. This created object +/// exposes some `window` functions, such as `.performance.now()`. It is enough for us to run the +/// generated WASM there. +pub fn get_window() -> Window { + js_sys::global().unchecked_into::() +} + #[cfg(target_arch = "wasm32")] -wasm_lazy_global! { window : Window = web_sys::window().unwrap() } +wasm_lazy_global! { window : Window = get_window() } #[cfg(target_arch = "wasm32")] wasm_lazy_global! { document : Document = window.document().unwrap() } diff --git a/package.json b/package.json index 7566626ec0..97eaabec29 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,11 @@ { "devDependencies": { - "prettier": "2.4.1" + "@typescript-eslint/eslint-plugin": "^5.48.1", + "@typescript-eslint/parser": "^5.48.1", + "prettier": "2.8.1" }, "dependencies": { - "chromedriver": "^106.0.1" + "chromedriver": "^106.0.1", + "typescript": "^4.9.4" } } diff --git a/tools/language-server/logstat/Cargo.toml b/tools/language-server/logstat/Cargo.toml index 4d46f8ccc6..23fe9ede82 100644 --- a/tools/language-server/logstat/Cargo.toml +++ b/tools/language-server/logstat/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" clap = { version = "3", features = ["derive"] } lazy_static = "1.4.0" enso-prelude = { path = "../../../lib/rust/prelude" } -regex = "1.6.0" +regex = { workspace = true } time = { version = "0.3", features = ["formatting", "parsing"] } tokio = { workspace = true } tokio-stream = { version = "0.1.9", features = ["io-util"] } diff --git a/tools/language-server/wstest/Cargo.toml b/tools/language-server/wstest/Cargo.toml index 41bcddcf8d..ab4aba16a5 100644 --- a/tools/language-server/wstest/Cargo.toml +++ b/tools/language-server/wstest/Cargo.toml @@ -10,7 +10,7 @@ clap = { version = "3", features = ["derive"] } either = "1.7.0" futures = "0.3" enso-prelude = { path = "../../../lib/rust/prelude" } -regex = "1.6.0" +regex = { workspace = true } time = { version = "0.3", features = ["formatting"] } tokio = { workspace = true } tokio-stream = { version = "0.1.9", features = ["io-util"] }