diff --git a/.github/workflows/backend-check.yml b/.github/workflows/backend-check.yml index 40f781937e..58a1926d6c 100644 --- a/.github/workflows/backend-check.yml +++ b/.github/workflows/backend-check.yml @@ -2,8 +2,8 @@ name: Backend check on: push: paths: - - "backend/**" - - ".github/workflows/backend-check.yml" + - "backend/**" + - ".github/workflows/backend-check.yml" jobs: check_oss: @@ -16,7 +16,7 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: cache-workspaces: backend - toolchain: 1.82.0 + toolchain: 1.83.0 - uses: Swatinem/rust-cache@v2 with: workspaces: backend @@ -32,16 +32,15 @@ jobs: with: fetch-depth: 0 - - name: install xmlsec1 run: | - sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev + sudo apt-get update + sudo apt-get install -y libxml2-dev libxmlsec1-dev - uses: actions-rust-lang/setup-rust-toolchain@v1 with: cache-workspaces: backend - toolchain: 1.82.0 + toolchain: 1.83.0 - uses: Swatinem/rust-cache@v2 with: workspaces: backend @@ -71,16 +70,14 @@ jobs: token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }} fetch-depth: 0 - - name: Substitute EE code (EE logic is behind feature flag) run: | ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private - - uses: actions-rust-lang/setup-rust-toolchain@v1 with: cache-workspaces: backend - toolchain: 1.82.0 + toolchain: 1.83.0 - uses: Swatinem/rust-cache@v2 with: workspaces: backend @@ -110,8 +107,8 @@ jobs: - name: install xmlsec1 run: | - sudo apt-get update - sudo apt-get install -y libxml2-dev libxmlsec1-dev + sudo apt-get update + sudo apt-get install -y libxml2-dev libxmlsec1-dev - name: Substitute EE code (EE logic is behind feature flag) run: | @@ -120,7 +117,7 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: cache-workspaces: backend - toolchain: 1.82.0 + toolchain: 1.83.0 - uses: Swatinem/rust-cache@v2 with: workspaces: backend @@ -130,5 +127,3 @@ jobs: run: | mkdir -p fake_frontend_build FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features - - \ No newline at end of file diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 4cbda91248..04668e96e0 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -54,7 +54,7 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: cache-workspaces: backend - toolchain: 1.82.0 + toolchain: 1.83.0 - uses: Swatinem/rust-cache@v2 with: workspaces: backend diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 109dfe9597..14735dc246 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -35,7 +35,7 @@ jobs: with: deno-version: v1.x - name: benchmark - timeout-minutes: 20 + timeout-minutes: 30 run: deno run --unstable -A -r https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts -c diff --git a/.github/workflows/build-publish-rh-image.yml b/.github/workflows/build-publish-rh-image.yml index dbfd152a9c..060f572fe8 100644 --- a/.github/workflows/build-publish-rh-image.yml +++ b/.github/workflows/build-publish-rh-image.yml @@ -64,7 +64,7 @@ jobs: platforms: linux/amd64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust secrets: | rh_username=${{ secrets.RH_USERNAME }} rh_password=${{ secrets.RH_PASSWORD }} @@ -81,7 +81,7 @@ jobs: platforms: linux/arm64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust secrets: | rh_username=${{ secrets.RH_USERNAME }} rh_password=${{ secrets.RH_PASSWORD }} diff --git a/.github/workflows/build_windows_worker_.yml b/.github/workflows/build_windows_worker_.yml index 70398725d8..6082a47e9b 100644 --- a/.github/workflows/build_windows_worker_.yml +++ b/.github/workflows/build_windows_worker_.yml @@ -45,7 +45,7 @@ jobs: $env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static" mkdir frontend/build && cd backend New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force - cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust + cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust - name: Rename binary with corresponding architecture run: | diff --git a/.github/workflows/deno_on_release.yml b/.github/workflows/deno_on_release.yml deleted file mode 100644 index 61f3c3d7e6..0000000000 --- a/.github/workflows/deno_on_release.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Publish deno-client -on: - push: - tags: - - "v*" -env: - repo: windmill-deno-client - -jobs: - build_deno_and_push_to_repo: - runs-on: ubicloud - steps: - - uses: actions/checkout@v4 - - name: generate_deno - run: | - cd deno-client - rm .gitignore - ./build.sh - - name: Pushes to another repository - id: push_directory - uses: cpina/github-action-push-to-another-repository@devel - env: - API_TOKEN_GITHUB: ${{ secrets.DENO_PAT }} - with: - source-directory: deno-client/ - destination-github-username: ${{ github.repository_owner }} - destination-repository-name: ${{ env.repo }} - user-email: ruben@windmill.dev - commit-message: See ORIGIN_COMMIT from $GITHUB_REF - target-branch: main - - tag_repo: - needs: [build_deno_and_push_to_repo] - runs-on: ubicloud - steps: - - uses: actions/checkout@v4 - with: - repository: ${{ github.repository_owner }}/${{ env.repo }} - token: ${{ secrets.DENO_PAT }} - path: ./client - - - name: Push client - run: | - cd ./client - git config --global user.email "ruben@windmill.dev" - git config --global user.name "rubenfiszel[bot]" - git tag -a ${{ github.ref_name }} -m "${{ github.ref_name }}" - git push --tags diff --git a/.github/workflows/docker-image-rpi4.yml b/.github/workflows/docker-image-rpi4.yml index 869787a4aa..c450723835 100644 --- a/.github/workflows/docker-image-rpi4.yml +++ b/.github/workflows/docker-image-rpi4.yml @@ -67,7 +67,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust + features=embedding,parquet,openidconnect,deno_core,license,http_trigger,zip,oauth2,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev ${{ steps.meta-public.outputs.tags }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 65512e1d84..18145886db 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -26,7 +26,11 @@ on: description: "Tag the image" required: true default: "test" - + nsjail: + description: "Build nsjail image (true, false)" + required: false + default: false + type: boolean concurrency: group: ${{ github.ref }} cancel-in-progress: true @@ -42,6 +46,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: ${{ github.ref }} fetch-depth: 0 - name: Read EE repo commit hash @@ -90,7 +95,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust + features=embedding,parquet,openidconnect,jemalloc,deno_core,license,http_trigger,zip,oauth2,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} ${{ steps.meta-public.outputs.tags }} @@ -100,10 +105,11 @@ jobs: build_ee: runs-on: ubicloud - if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee) + if: (github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail) steps: - uses: actions/checkout@v4 with: + ref: ${{ github.ref }} fetch-depth: 0 - name: Read EE repo commit hash @@ -152,7 +158,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }} ${{ steps.meta-ee-public.outputs.tags }} @@ -160,68 +166,6 @@ jobs: ${{ steps.meta-ee-public.outputs.labels }} org.opencontainers.image.licenses=Windmill-Enterprise-License - build_ee_312: - runs-on: ubicloud - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Read EE repo commit hash - run: | - echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV" - - - uses: actions/checkout@v4 - with: - repository: windmill-labs/windmill-ee-private - path: ./windmill-ee-private - ref: ${{ env.ee_repo_ref }} - token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }} - fetch-depth: 0 - - # - name: Set up Docker Buildx - # uses: docker/setup-buildx-action@v2 - - uses: depot/setup-action@v1 - - - name: Docker meta - id: meta-ee-public-py312 - uses: docker/metadata-action@v5 - with: - images: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-py312 - flavor: | - latest=false - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Login to registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Substitute EE code - run: | - ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private - - - name: Build and push publicly ee - uses: depot/build-push-action@v1 - with: - context: . - platforms: linux/amd64 - push: true - build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,otel,dind,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust - PYTHON_IMAGE=python:3.12.2-slim-bookworm - tags: | - ${{ steps.meta-ee-public-py312.outputs.tags }} - labels: | - ${{ steps.meta-ee-public-py312.outputs.labels }} - org.opencontainers.image.licenses=Windmill-Enterprise-License - # disabled until we make it 100% reliable and add more meaningful tests # playwright: # runs-on: [self-hosted, new] @@ -263,7 +207,6 @@ jobs: ARCH: amd64 steps: - uses: actions/checkout@v4 - - run: | # pulling docker image with desired arch so that actions-docker-extract doesn't do it docker pull --platform "linux/$ARCH" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }} @@ -349,6 +292,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: ${{ github.ref }} fetch-depth: 0 - name: Prepare test run if: ${{ ! startsWith(github.ref, 'refs/tags/v') }} @@ -372,7 +316,7 @@ jobs: needs: [run_integration_test, build] if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || - startsWith(github.ref, 'refs/tags/v')) + startsWith(github.ref, 'refs/tags/v')) && (github.event_name != 'workflow_dispatch') steps: - uses: actions/checkout@v4 with: @@ -392,7 +336,7 @@ jobs: runs-on: ubicloud needs: [run_integration_test, build_ee] if: - github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || + github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch') && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) steps: - uses: actions/checkout@v4 @@ -412,7 +356,7 @@ jobs: verify_ee_image_vulnerabilities: runs-on: ubicloud needs: [tag_latest_ee] - if: ${{ startsWith(github.ref, 'refs/tags/v') }} + if: startsWith(github.ref, 'refs/tags/v') && (github.event_name != 'workflow_dispatch') steps: - name: Checkout code uses: actions/checkout@v4 @@ -455,12 +399,13 @@ jobs: needs: [build_ee] runs-on: ubicloud if: - (github.event_name != 'pull_request') && (github.event_name != - 'workflow_dispatch') + (github.event_name != 'pull_request') && ((github.event_name != 'workflow_dispatch') || (github.event.inputs.ee || github.event.inputs.nsjail)) + steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ github.ref }} # - name: Set up Docker Buildx # uses: docker/setup-buildx-action@v2 @@ -489,6 +434,11 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Update Dockerfile image reference + run: | + sed -i 's|FROM ghcr.io/windmill-labs/windmill-ee:dev|FROM ghcr.io/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}|' ./docker/DockerfileNsjail + cat ./docker/DockerfileNsjail | grep "FROM" + - name: Build and push publicly ee uses: depot/build-push-action@v1 with: @@ -505,7 +455,7 @@ jobs: build_ee_reports_privately: needs: [build_ee_nsjail] runs-on: ubicloud - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch') steps: - uses: actions/checkout@v4 with: @@ -600,6 +550,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ github.ref }} # - name: Set up Docker Buildx # uses: docker/setup-buildx-action@v2 diff --git a/.github/workflows/publish_windows_worker.yml b/.github/workflows/publish_windows_worker.yml index e6854020be..b97d3b5560 100644 --- a/.github/workflows/publish_windows_worker.yml +++ b/.github/workflows/publish_windows_worker.yml @@ -47,7 +47,7 @@ jobs: $env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static" mkdir frontend/build && cd backend New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force - cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,websocket,python,smtp,csharp,static_frontend,rust + cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core,license,http_trigger,zip,oauth2,kafka,nats,php,mysql,mssql,bigquery,oracledb,postgres_trigger,websocket,python,smtp,csharp,static_frontend,rust - name: Rename binary with corresponding architecture run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index d4de463cfa..de653bcba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,128 @@ # Changelog +## [1.454.1](https://github.com/windmill-labs/windmill/compare/v1.454.0...v1.454.1) (2025-01-30) + + +### Bug Fixes + +* fix codebase script execution ([b1155a9](https://github.com/windmill-labs/windmill/commit/b1155a9012c726c1b1c3f1a62f3aa96b8504681c)) + +## [1.454.0](https://github.com/windmill-labs/windmill/compare/v1.453.1...v1.454.0) (2025-01-30) + + +### Features + +* history for flow inputs ([#5117](https://github.com/windmill-labs/windmill/issues/5117)) ([cd44056](https://github.com/windmill-labs/windmill/commit/cd440564d29aff39f6dc30ecbf0055696b07cf12)) + + +### Bug Fixes + +* fix app form password handling ([4812c0c](https://github.com/windmill-labs/windmill/commit/4812c0cc9e46fdb5944f7010efdf617f2fd23cff)) +* improve codebase support ([c8cc9d2](https://github.com/windmill-labs/windmill/commit/c8cc9d2941dda2fedb959e1156c4f9b4f7dfab43)) + +## [1.453.1](https://github.com/windmill-labs/windmill/compare/v1.453.0...v1.453.1) (2025-01-30) + + +### Bug Fixes + +* **frontend:** fetching job result (db schema) separatly if too big ([#5171](https://github.com/windmill-labs/windmill/issues/5171)) ([f29ee67](https://github.com/windmill-labs/windmill/commit/f29ee67d9d8547d7c4a79e14698b7d88a722e730)) +* **typescript-client:** fix typescript client mocked api behavior ([7f516f0](https://github.com/windmill-labs/windmill/commit/7f516f08682876f5a7ca409ec002cba6ec0c5c5b)) + +## [1.453.0](https://github.com/windmill-labs/windmill/compare/v1.452.1...v1.453.0) (2025-01-29) + + +### Features + +* custom response headers ([#5156](https://github.com/windmill-labs/windmill/issues/5156)) ([5544d42](https://github.com/windmill-labs/windmill/commit/5544d42a2db652660051b5f2e05f8db0102400fa)) +* **python:** add `custom_wheels` directory to `PYTHONPATH` ([#5169](https://github.com/windmill-labs/windmill/issues/5169)) ([c60f8da](https://github.com/windmill-labs/windmill/commit/c60f8dacf191c5255ef8fca462d6d3d571b86b07)) +* websocket trigger allow returning messages ([#5168](https://github.com/windmill-labs/windmill/issues/5168)) ([487c273](https://github.com/windmill-labs/windmill/commit/487c273bfbc5bf1731947c922f32cd026cca47f9)) + + +### Bug Fixes + +* oracledb tag for native workers + client not working on arm systems ([#5162](https://github.com/windmill-labs/windmill/issues/5162)) ([a1579c1](https://github.com/windmill-labs/windmill/commit/a1579c1654a2ddf9f6fc8f05f62b37bb09303752)) +* **python:** fix uv can't find ssl certificates ([#5157](https://github.com/windmill-labs/windmill/issues/5157)) ([680fb18](https://github.com/windmill-labs/windmill/commit/680fb1852a192e384e23e2f5ceea473383f9f5aa)) + +## [1.452.1](https://github.com/windmill-labs/windmill/compare/v1.452.0...v1.452.1) (2025-01-27) + + +### Bug Fixes + +* fix broken tabs for apps ([eaf633c](https://github.com/windmill-labs/windmill/commit/eaf633ce7675bf86108ebfc81ed735c26031ad71)) + +## [1.452.0](https://github.com/windmill-labs/windmill/compare/v1.451.0...v1.452.0) (2025-01-27) + + +### Features + +* mssql executor supports azure db redirect ([#5150](https://github.com/windmill-labs/windmill/issues/5150)) ([fdb7122](https://github.com/windmill-labs/windmill/commit/fdb712234eb7f1a9374012d51e1cb25ee5aaa410)) +* **postgres, trigger:** support ssl ([#5149](https://github.com/windmill-labs/windmill/issues/5149)) ([e0b6aa4](https://github.com/windmill-labs/windmill/commit/e0b6aa42a28a4273f3a7bb7465396f2756e60a92)) +* test trigger connection ([#5145](https://github.com/windmill-labs/windmill/issues/5145)) ([d17397b](https://github.com/windmill-labs/windmill/commit/d17397b0703fb6a7dabf9db75d3c30ec26cdbe1e)) + +## [1.451.0](https://github.com/windmill-labs/windmill/compare/v1.450.1...v1.451.0) (2025-01-27) + + +### Features + +* app builder accordion list and accordion tab component ([#5132](https://github.com/windmill-labs/windmill/issues/5132)) ([0e41023](https://github.com/windmill-labs/windmill/commit/0e41023889fb2f26ef5dc2a736dd59b819d92bf1)) +* **typescript-client:** add ability to mock typescript api for testing ([f9ce01c](https://github.com/windmill-labs/windmill/commit/f9ce01c1dcda581d943e3cf58008cc6c89d39c5f)) + + +### Bug Fixes + +* **frontend:** make argInput indent optional ([#5144](https://github.com/windmill-labs/windmill/issues/5144)) ([b8152a3](https://github.com/windmill-labs/windmill/commit/b8152a3259fa0f791990e41d825f196d82c4084d)) + +## [1.450.1](https://github.com/windmill-labs/windmill/compare/v1.450.0...v1.450.1) (2025-01-26) + + +### Bug Fixes + +* fix SCIM/SAML settings persistence ([697a060](https://github.com/windmill-labs/windmill/commit/697a060e3e01a183b6ac2371eee319b01ee891cf)) + +## [1.450.0](https://github.com/windmill-labs/windmill/compare/v1.449.3...v1.450.0) (2025-01-26) + + +### Features + +* **cli:** improve codebase support + remove --stateful + warn when pushing stale metadata ([#5139](https://github.com/windmill-labs/windmill/issues/5139)) ([b47e9c1](https://github.com/windmill-labs/windmill/commit/b47e9c14f277339ae51fd3a40e00e8ba4e8a1390)) + +## [1.449.3](https://github.com/windmill-labs/windmill/compare/v1.449.2...v1.449.3) (2025-01-25) + + +### Bug Fixes + +* include postgres triggers in build [#5137](https://github.com/windmill-labs/windmill/issues/5137) ([27a25d4](https://github.com/windmill-labs/windmill/commit/27a25d4af9ca0f0e0ed1d06a462fdd7cf966ce2b)) +* update tests to rust 1.83.0 ([f29c702](https://github.com/windmill-labs/windmill/commit/f29c70279f14c2cdbf6e330ae266060f81f32bb6)) + +## [1.449.2](https://github.com/windmill-labs/windmill/compare/v1.449.1...v1.449.2) (2025-01-25) + + +### Bug Fixes + +* update rust to 1.83.0 ([#5135](https://github.com/windmill-labs/windmill/issues/5135)) ([5381c76](https://github.com/windmill-labs/windmill/commit/5381c76b7d194d75f58d70b29aa3f5f8589fa899)) + +## [1.449.1](https://github.com/windmill-labs/windmill/compare/v1.449.0...v1.449.1) (2025-01-25) + + +### Bug Fixes + +* Fix python refetching wheels from S3 ([#5133](https://github.com/windmill-labs/windmill/issues/5133)) ([2f1804b](https://github.com/windmill-labs/windmill/commit/2f1804b6d0e6f3492608967f8937d5e34481661b)) + +## [1.449.0](https://github.com/windmill-labs/windmill/compare/v1.448.1...v1.449.0) (2025-01-24) + + +### Features + +* **frontend:** operator visibility setting per workspace ([#5124](https://github.com/windmill-labs/windmill/issues/5124)) ([1a1ea68](https://github.com/windmill-labs/windmill/commit/1a1ea682166af62133d3df2816490e3ebc92462f)) +* msft teams support for critical alerts ([#5113](https://github.com/windmill-labs/windmill/issues/5113)) ([a88fbb2](https://github.com/windmill-labs/windmill/commit/a88fbb238ae21ca53bccd53ccfd3c77e2946fda2)) +* postgres triggers ([#4860](https://github.com/windmill-labs/windmill/issues/4860)) ([316a216](https://github.com/windmill-labs/windmill/commit/316a2167e7648052afb3d7456c5ab9324348e56c)) +* **python:** Multiple runtime versions ([#4579](https://github.com/windmill-labs/windmill/issues/4579)) ([e47dd69](https://github.com/windmill-labs/windmill/commit/e47dd697f96883f8f97bf469e69d3344f12d9633)) + + +### Bug Fixes + +* **bun, windows:** Fix "A required privilege is not held by the client." ([#5126](https://github.com/windmill-labs/windmill/issues/5126)) ([74bb660](https://github.com/windmill-labs/windmill/commit/74bb660b9799e79e31c33d96a4f06d77094287d5)) + ## [1.448.1](https://github.com/windmill-labs/windmill/compare/v1.448.0...v1.448.1) (2025-01-22) diff --git a/Dockerfile b/Dockerfile index 1cddb360d9..35462db96a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG DEBIAN_IMAGE=debian:bookworm-slim -ARG RUST_IMAGE=rust:1.82-slim-bookworm +ARG RUST_IMAGE=rust:1.83-slim-bookworm ARG PYTHON_IMAGE=python:3.11.10-slim-bookworm FROM ${RUST_IMAGE} AS rust_base diff --git a/backend/.sqlx/query-6c845f168b6265e6cf92e4d33c5409edfdc1847d0348df2ea55504ddaaa67736.json b/backend/.sqlx/query-1452033a8e2b160883a649c986d6c7ba2f60f41e1abb6ff8332bd2dfa7379d14.json similarity index 53% rename from backend/.sqlx/query-6c845f168b6265e6cf92e4d33c5409edfdc1847d0348df2ea55504ddaaa67736.json rename to backend/.sqlx/query-1452033a8e2b160883a649c986d6c7ba2f60f41e1abb6ff8332bd2dfa7379d14.json index b4199d4ca1..1bad8b94d0 100644 --- a/backend/.sqlx/query-6c845f168b6265e6cf92e4d33c5409edfdc1847d0348df2ea55504ddaaa67736.json +++ b/backend/.sqlx/query-1452033a8e2b160883a649c986d6c7ba2f60f41e1abb6ff8332bd2dfa7379d14.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false", + "query": "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color,\n CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings\n FROM workspace\n JOIN usr ON usr.workspace_id = workspace.id\n JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id\n WHERE usr.email = $1 AND workspace.deleted = false", "describe": { "columns": [ { @@ -22,6 +22,11 @@ "ordinal": 3, "name": "color", "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "operator_settings", + "type_info": "Jsonb" } ], "parameters": { @@ -33,8 +38,9 @@ false, false, false, - true + true, + null ] }, - "hash": "6c845f168b6265e6cf92e4d33c5409edfdc1847d0348df2ea55504ddaaa67736" + "hash": "1452033a8e2b160883a649c986d6c7ba2f60f41e1abb6ff8332bd2dfa7379d14" } diff --git a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json b/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json index 64d273ed70..b6fee2c5ff 100644 --- a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json +++ b/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json @@ -127,6 +127,11 @@ "ordinal": 24, "name": "color", "type_info": "Varchar" + }, + { + "ordinal": 25, + "name": "operator_settings", + "type_info": "Jsonb" } ], "parameters": { @@ -159,6 +164,7 @@ true, true, true, + true, true ] }, diff --git a/backend/.sqlx/query-1ef48cc430870ab6c062b046bdbf0a2db057141619e9818ddb904d7c242efec3.json b/backend/.sqlx/query-1ef48cc430870ab6c062b046bdbf0a2db057141619e9818ddb904d7c242efec3.json deleted file mode 100644 index 7fc5a4c430..0000000000 --- a/backend/.sqlx/query-1ef48cc430870ab6c062b046bdbf0a2db057141619e9818ddb904d7c242efec3.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT \n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) as \"websocket_used!\", \n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) as \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\"", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "websocket_used!", - "type_info": "Bool" - }, - { - "ordinal": 1, - "name": "http_routes_used!", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "kafka_used!", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "nats_used!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null, - null, - null, - null - ] - }, - "hash": "1ef48cc430870ab6c062b046bdbf0a2db057141619e9818ddb904d7c242efec3" -} diff --git a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json index 6092b0551a..baf7cb42f5 100644 --- a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json +++ b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json @@ -127,6 +127,11 @@ "ordinal": 24, "name": "color", "type_info": "Varchar" + }, + { + "ordinal": 25, + "name": "operator_settings", + "type_info": "Jsonb" } ], "parameters": { @@ -159,6 +164,7 @@ true, true, true, + true, true ] }, diff --git a/backend/.sqlx/query-597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3.json b/backend/.sqlx/query-597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3.json new file mode 100644 index 0000000000..57989bb6de --- /dev/null +++ b/backend/.sqlx/query-597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET operator_settings = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "597b89889c3820fe7986b834c0e5a0652d6a72024a0c17f5271add38168d1ab3" +} diff --git a/backend/.sqlx/query-e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4.json b/backend/.sqlx/query-5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd.json similarity index 73% rename from backend/.sqlx/query-e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4.json rename to backend/.sqlx/query-5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd.json index b2ff93f8d9..890a282ad1 100644 --- a/backend/.sqlx/query-e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4.json +++ b/backend/.sqlx/query-5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6", + "query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as \"payload!: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6", "describe": { "columns": [ { @@ -34,7 +34,7 @@ }, { "ordinal": 3, - "name": "payload: _", + "name": "payload!: _", "type_info": "Jsonb" }, { @@ -71,9 +71,9 @@ false, false, false, - false, + null, true ] }, - "hash": "e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4" + "hash": "5c1de8473e0e96c1063a9a735a064c5a91e3ed8d9260c72b783fc12542b88fbd" } diff --git a/backend/.sqlx/query-561b7935d687f6b9f3d6488f8489f55e6737ffa2d3716f803d32f8b68cc1e915.json b/backend/.sqlx/query-63b42286804a3f3977235935d7ac200a07c5a4ad3a50051b73f382385b89466e.json similarity index 62% rename from backend/.sqlx/query-561b7935d687f6b9f3d6488f8489f55e6737ffa2d3716f803d32f8b68cc1e915.json rename to backend/.sqlx/query-63b42286804a3f3977235935d7ac200a07c5a4ad3a50051b73f382385b89466e.json index a3e2f3d338..3938de3ddf 100644 --- a/backend/.sqlx/query-561b7935d687f6b9f3d6488f8489f55e6737ffa2d3716f803d32f8b68cc1e915.json +++ b/backend/.sqlx/query-63b42286804a3f3977235935d7ac200a07c5a4ad3a50051b73f382385b89466e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $10 AND path = $11", + "query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, can_return_message = $10, edited_at = now(), server_id = NULL, error = NULL\n WHERE workspace_id = $11 AND path = $12", "describe": { "columns": [], "parameters": { @@ -14,11 +14,12 @@ "Jsonb", "Varchar", "Varchar", + "Bool", "Text", "Text" ] }, "nullable": [] }, - "hash": "561b7935d687f6b9f3d6488f8489f55e6737ffa2d3716f803d32f8b68cc1e915" + "hash": "63b42286804a3f3977235935d7ac200a07c5a4ad3a50051b73f382385b89466e" } diff --git a/backend/.sqlx/query-64920b845c0ce81fb99497c03b249bb6cb06581079b5fc5bea5ddd8e7a895b79.json b/backend/.sqlx/query-64920b845c0ce81fb99497c03b249bb6cb06581079b5fc5bea5ddd8e7a895b79.json deleted file mode 100644 index d201c1c08e..0000000000 --- a/backend/.sqlx/query-64920b845c0ce81fb99497c03b249bb6cb06581079b5fc5bea5ddd8e7a895b79.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "select path from script where hash = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "64920b845c0ce81fb99497c03b249bb6cb06581079b5fc5bea5ddd8e7a895b79" -} diff --git a/backend/.sqlx/query-84576f6add15108ade2ed3a88185d67db17904859e5f01f1d578af4e1d062a85.json b/backend/.sqlx/query-84576f6add15108ade2ed3a88185d67db17904859e5f01f1d578af4e1d062a85.json deleted file mode 100644 index cdcf0228ca..0000000000 --- a/backend/.sqlx/query-84576f6add15108ade2ed3a88185d67db17904859e5f01f1d578af4e1d062a85.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "last_ping", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - true - ] - }, - "hash": "84576f6add15108ade2ed3a88185d67db17904859e5f01f1d578af4e1d062a85" -} diff --git a/backend/.sqlx/query-9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357.json b/backend/.sqlx/query-9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357.json new file mode 100644 index 0000000000..8ae3d80ca6 --- /dev/null +++ b/backend/.sqlx/query-9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH zombie_jobs AS (\n UPDATE queue SET running = false, started_at = null\n WHERE last_ping < now() - ($1 || ' seconds')::interval\n AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false \n RETURNING id, workspace_id, last_ping\n ),\n update_concurrency AS (\n UPDATE concurrency_counter cc\n SET job_uuids = job_uuids - zj.id::text\n FROM zombie_jobs zj\n INNER JOIN concurrency_key ck ON ck.job_id = zj.id\n WHERE cc.concurrency_id = ck.key\n )\n SELECT id, workspace_id, last_ping FROM zombie_jobs", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "last_ping", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true + ] + }, + "hash": "9a9b639611459659ae355a43f219c6da2c3d1e04d49306adaaaaf06e54ee8357" +} diff --git a/backend/.sqlx/query-0bf123446bebbc357c58a53a9319f4954dbf3225e91cbe999e5b264c1a747664.json b/backend/.sqlx/query-d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799.json similarity index 84% rename from backend/.sqlx/query-0bf123446bebbc357c58a53a9319f4954dbf3225e91cbe999e5b264c1a747664.json rename to backend/.sqlx/query-d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799.json index a7c52db593..93a338c13f 100644 --- a/backend/.sqlx/query-0bf123446bebbc357c58a53a9319f4954dbf3225e91cbe999e5b264c1a747664.json +++ b/backend/.sqlx/query-d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option\", envs AS \"envs: Vec\", codebase AS \"codebase: String\" FROM script WHERE hash = $1 LIMIT 1", + "query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option\", envs AS \"envs: Vec\", codebase LIKE '%.tar' as use_tar FROM script WHERE hash = $1 LIMIT 1", "describe": { "columns": [ { @@ -52,8 +52,8 @@ }, { "ordinal": 4, - "name": "codebase: String", - "type_info": "Varchar" + "name": "use_tar", + "type_info": "Bool" } ], "parameters": { @@ -66,8 +66,8 @@ true, false, true, - true + null ] }, - "hash": "0bf123446bebbc357c58a53a9319f4954dbf3225e91cbe999e5b264c1a747664" + "hash": "d55eabd42559893bdf91c5fdab1ae58006013be26fcb9367b3c6830f38749799" } diff --git a/backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json b/backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json new file mode 100644 index 0000000000..2ef165828e --- /dev/null +++ b/backend/.sqlx/query-e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7.json @@ -0,0 +1,61 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload!: _\", trigger_extra as \"trigger_extra: _\" FROM capture WHERE id = $1 AND workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "trigger_kind: _", + "type_info": { + "Custom": { + "name": "trigger_kind", + "kind": { + "Enum": [ + "webhook", + "http", + "websocket", + "kafka", + "email", + "nats" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "payload!: _", + "type_info": "Jsonb" + }, + { + "ordinal": 4, + "name": "trigger_extra: _", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Int8", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + true + ] + }, + "hash": "e17ec84003e2ec414622d100f5dfdda86bee33f31835317df512a20c805b35d7" +} diff --git a/backend/.sqlx/query-f3d3d105b76ec2eded521c3bb3735a3765c9aa2d103e3df85c245d85ba32b3b9.json b/backend/.sqlx/query-f3d3d105b76ec2eded521c3bb3735a3765c9aa2d103e3df85c245d85ba32b3b9.json deleted file mode 100644 index 4f5d515e96..0000000000 --- a/backend/.sqlx/query-f3d3d105b76ec2eded521c3bb3735a3765c9aa2d103e3df85c245d85ba32b3b9.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT value, is_secret\n FROM variable\n WHERE path = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "value", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "is_secret", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "f3d3d105b76ec2eded521c3bb3735a3765c9aa2d103e3df85c245d85ba32b3b9" -} diff --git a/backend/.sqlx/query-fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1.json b/backend/.sqlx/query-fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1.json new file mode 100644 index 0000000000..57ae674eb4 --- /dev/null +++ b/backend/.sqlx/query-fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT flow_version.value->>'early_return' as early_return\n FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 and flow.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "early_return", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "fc243af1bc70f04e28c006364d6dfdfc49981568e8c496fb6a163c499c3e4ad1" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index aa79cf5e13..3332125211 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -67,7 +67,7 @@ dependencies = [ "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -651,9 +651,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.5.14" +version = "1.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f40e82e858e02445402906e454a73e244c7f501fcae198977585946c48e8697" +checksum = "dc47e70fc35d054c8fcd296d47a61711f043ac80534a10b4f741904f81e73a90" dependencies = [ "aws-credential-types", "aws-runtime", @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.55.0" +version = "1.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33993c0b054f4251ff2946941b56c26b582677303eeca34087594eb901ece022" +checksum = "c54bab121fe1881a74c338c5f723d1592bf3b53167f80268a1274f404e1acc38" dependencies = [ "aws-credential-types", "aws-runtime", @@ -740,9 +740,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.56.0" +version = "1.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd3ceba74a584337a8f3839c818f14f1a2288bfd24235120ff22d7e17a0dd54" +checksum = "8c8234fd024f7ac61c4e44ea008029bde934250f371efe7d4a39708397b1080c" dependencies = [ "aws-credential-types", "aws-runtime", @@ -762,9 +762,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.56.0" +version = "1.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07835598e52dd354368429cb2abf447ce523ea446d0a533a63cb42cd0d2d9280" +checksum = "ba60e1d519d6f23a9df712c04fdeadd7872ac911c84b2f62a8bda92e129b7962" dependencies = [ "aws-credential-types", "aws-runtime", @@ -962,7 +962,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "itoa", "matchit", @@ -1282,7 +1282,7 @@ dependencies = [ "hex", "http 1.2.0", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-named-pipe", "hyper-util", "hyperlocal", @@ -1388,9 +1388,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" dependencies = [ "allocator-api2", ] @@ -1708,9 +1708,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmake" -version = "0.1.52" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" dependencies = [ "cc", ] @@ -1893,9 +1893,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -1941,7 +1941,7 @@ checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" dependencies = [ "chrono", "once_cell", - "winnow 0.6.24", + "winnow 0.6.26", ] [[package]] @@ -2641,7 +2641,7 @@ dependencies = [ "hickory-resolver", "http 1.2.0", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-rustls 0.27.5", "hyper-util", "ipnet", @@ -2661,9 +2661,9 @@ dependencies = [ [[package]] name = "deno_media_type" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577fe2bbe04f3e9b1b7c6fac6a75101a9fbd611c50a6b68789e69f4d63dcb2b4" +checksum = "600222d059ab31ff31182b3e12615df2134a9e01605836b78ad8df91ba39eab3" dependencies = [ "data-url", "serde", @@ -3764,6 +3764,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + [[package]] name = "gimli" version = "0.31.1" @@ -3944,6 +3956,17 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashify" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f208758247e68e239acaa059e72e4ce1f30f2a4b6523f19c1b923d25b7e9cceb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "hashlink" version = "0.10.0" @@ -4081,9 +4104,9 @@ dependencies = [ [[package]] name = "hstr" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d6824358c0fd9a68bb23999ed2ef76c84f79408a26ef7ae53d5f370c94ad36" +checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", @@ -4178,9 +4201,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -4220,9 +4243,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -4246,7 +4269,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", @@ -4278,7 +4301,7 @@ checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http 1.2.0", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "rustls 0.23.21", "rustls-native-certs 0.8.1", @@ -4295,7 +4318,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" dependencies = [ - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", @@ -4323,7 +4346,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "native-tls", "tokio", @@ -4342,7 +4365,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.2", + "hyper 1.6.0", "pin-project-lite", "socket2", "tokio", @@ -4359,7 +4382,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", @@ -4564,9 +4587,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.9" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ "console", "number_prefix", @@ -5037,11 +5060,12 @@ dependencies = [ [[package]] name = "mail-parser" -version = "0.9.4" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93c3b9e5d8b17faf573330bbc43b37d6e918c0a3bf8a88e7d0a220ebc84af9fc" +checksum = "187a2b93c4c8c32f552ee06c2d99915e575de2fc7e04b07891c9edfee5b8edd6" dependencies = [ "encoding_rs", + "hashify", "serde", ] @@ -5064,9 +5088,9 @@ dependencies = [ [[package]] name = "malachite" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5616515d632967cd329b6f6db96be9a03ea0b3a49cdbc45b0016803dad8a77b7" +checksum = "4a6ecab92657eb234bfe98abd0b17920772c6b14ce69256950142e2eb36d000b" dependencies = [ "malachite-base", "malachite-nz", @@ -5075,9 +5099,9 @@ dependencies = [ [[package]] name = "malachite-base" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46059721011b0458b7bd6d9179be5d0b60294281c23320c207adceaecc54d13b" +checksum = "06f6d078bb4dad5b76f6a85737e6c9113667d199612a73eef375c56e75d5d4d5" dependencies = [ "hashbrown 0.14.5", "itertools 0.11.0", @@ -5100,9 +5124,9 @@ dependencies = [ [[package]] name = "malachite-nz" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1503b27e825cabd1c3d0ff1e95a39fb2ec9eab6fd3da6cfa41aec7091d273e78" +checksum = "ad61a09cde72dcdb8b2baaf3e71430c5bd94fb6753da89f17301a459f3e466db" dependencies = [ "itertools 0.11.0", "libm", @@ -5111,9 +5135,9 @@ dependencies = [ [[package]] name = "malachite-q" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a475503a70a3679dbe3b9b230a23622516742528ba614a7b2490f180ea9cb514" +checksum = "be4a9dfffb87667ae94e8320213d3f5419e3e37311bc6bf8f4d2ab9b44f3a535" dependencies = [ "itertools 0.11.0", "malachite-base", @@ -5388,9 +5412,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -5642,7 +5666,7 @@ dependencies = [ "chrono", "futures", "humantime", - "hyper 1.5.2", + "hyper 1.6.0", "itertools 0.13.0", "md-5 0.10.6", "parking_lot", @@ -5753,9 +5777,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -5779,9 +5803,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" @@ -6274,6 +6298,17 @@ dependencies = [ "tokio-postgres 0.7.12", ] +[[package]] +name = "postgres-native-tls" +version = "0.5.0" +source = "git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b#20265ef38e32a06f76b6f9b678e2077fc2211f6b" +dependencies = [ + "native-tls", + "tokio", + "tokio-native-tls", + "tokio-postgres 0.7.11", +] + [[package]] name = "postgres-protocol" version = "0.6.7" @@ -6348,7 +6383,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -6755,6 +6790,17 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.14", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -6775,6 +6821,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -6793,6 +6849,16 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.14", +] + [[package]] name = "rand_distr" version = "0.4.3" @@ -7052,7 +7118,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-rustls 0.27.5", "hyper-tls 0.6.0", "hyper-util", @@ -7401,9 +7467,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" dependencies = [ "web-time", ] @@ -7506,9 +7572,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "ryu-js" @@ -7776,9 +7842,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "indexmap 2.7.1", "itoa", @@ -9233,13 +9299,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand 2.3.0", - "getrandom 0.2.15", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -9762,7 +9828,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.24", + "winnow 0.6.26", ] [[package]] @@ -9780,7 +9846,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -10280,9 +10346,9 @@ checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -10554,6 +10620,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasite" version = "0.1.0" @@ -10785,7 +10860,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "axum", @@ -10802,7 +10877,7 @@ dependencies = [ "once_cell", "prometheus", "quote", - "rand 0.8.5", + "rand 0.9.0", "reqwest 0.12.9", "serde", "serde_json", @@ -10815,6 +10890,7 @@ dependencies = [ "tracing", "url", "uuid 1.12.1", + "v8", "windmill-api", "windmill-api-client", "windmill-autoscaling", @@ -10827,7 +10903,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "argon2", @@ -10857,7 +10933,7 @@ dependencies = [ "hf-hub", "hmac", "http 1.2.0", - "hyper 1.5.2", + "hyper 1.6.0", "itertools 0.14.0", "jsonwebtoken", "lazy_static", @@ -10872,9 +10948,10 @@ dependencies = [ "openssl", "pg_escape", "pin-project", + "postgres-native-tls 0.5.0 (git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b)", "prometheus", "quick_cache", - "rand 0.8.5", + "rand 0.9.0", "rdkafka", "regex", "reqwest 0.12.9", @@ -10919,7 +10996,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.448.1" +version = "1.454.1" dependencies = [ "base64 0.22.1", "chrono", @@ -10927,7 +11004,7 @@ dependencies = [ "prettyplease 0.1.25", "progenitor", "progenitor-client", - "rand 0.8.5", + "rand 0.9.0", "reqwest 0.11.27", "serde", "serde_json", @@ -10937,7 +11014,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.448.1" +version = "1.454.1" dependencies = [ "chrono", "serde", @@ -10950,7 +11027,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "serde", @@ -10964,7 +11041,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "async-stream", @@ -10984,7 +11061,7 @@ dependencies = [ "git-version", "hex", "hmac", - "hyper 1.5.2", + "hyper 1.6.0", "indexmap 2.7.1", "itertools 0.14.0", "lazy_static", @@ -10999,7 +11076,7 @@ dependencies = [ "pin-project-lite", "prometheus", "quick_cache", - "rand 0.8.5", + "rand 0.9.0", "regex", "reqwest 0.12.9", "semver 1.0.25", @@ -11023,7 +11100,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.448.1" +version = "1.454.1" dependencies = [ "regex", "serde", @@ -11037,7 +11114,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "bytes", @@ -11060,7 +11137,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.448.1" +version = "1.454.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -11072,7 +11149,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.448.1" +version = "1.454.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -11081,7 +11158,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "lazy_static", @@ -11093,7 +11170,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "serde_json", @@ -11105,7 +11182,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "gosyn", @@ -11117,7 +11194,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "lazy_static", @@ -11129,7 +11206,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -11140,7 +11217,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -11151,7 +11228,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "async-recursion", @@ -11169,7 +11246,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -11186,7 +11263,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "lazy_static", @@ -11198,7 +11275,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "lazy_static", @@ -11216,7 +11293,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -11238,7 +11315,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "serde_json", @@ -11248,7 +11325,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "async-recursion", @@ -11281,7 +11358,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.448.1" +version = "1.454.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -11291,7 +11368,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.448.1" +version = "1.454.1" dependencies = [ "anyhow", "async-recursion", @@ -11331,9 +11408,9 @@ dependencies = [ "opentelemetry", "oracle", "pem 3.0.4", - "postgres-native-tls", + "postgres-native-tls 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "prometheus", - "rand 0.8.5", + "rand 0.9.0", "regex", "reqwest 0.12.9", "rust_decimal", @@ -11566,9 +11643,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.24" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" dependencies = [ "memchr", ] @@ -11589,6 +11666,15 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -11682,7 +11768,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive 0.8.14", ] [[package]] @@ -11696,6 +11791,17 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2ef62c9f57..3890441063 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.448.1" +version = "1.454.1" authors.workspace = true edition.workspace = true @@ -30,7 +30,7 @@ members = [ ] [workspace.package] -version = "1.448.1" +version = "1.454.1" authors = ["Ruben Fiszel "] edition = "2021" @@ -61,7 +61,7 @@ cloud = ["windmill-queue/cloud", "windmill-worker/cloud"] jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"] tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "enterprise", "parquet"] sqlx = ["windmill-worker/sqlx"] -deno_core = ["windmill-worker/deno_core", "dep:deno_core"] +deno_core = ["windmill-worker/deno_core", "dep:deno_core", "dep:v8"] kafka = ["windmill-api/kafka"] nats = ["windmill-api/nats"] otel = ["windmill-common/otel", "windmill-worker/otel"] @@ -116,6 +116,7 @@ deno_core = { workspace = true, optional = true } object_store = { workspace = true, optional = true } quote.workspace = true memchr.workspace = true +v8 = { workspace = true, optional = true } [target.'cfg(not(target_env = "msvc"))'.dependencies] @@ -133,6 +134,7 @@ windmill-api-client.workspace = true deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] } + [workspace.dependencies] windmill-api = { path = "./windmill-api", default-features = false } windmill-queue = { path = "./windmill-queue" } @@ -157,6 +159,7 @@ windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" } windmill-parser-php = { path = "./parsers/windmill-parser-php" } windmill-api-client = { path = "./windmill-api-client" } +v8 = "=130.0.7" # Exact version memchr = "2.7.4" axum = { version = "^0.7", features = ["multipart"] } headers = "^0" @@ -265,6 +268,7 @@ convert_case = "0.6.0" getrandom = "0.2" tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-1", "with-chrono-0_4", "with-uuid-1", "with-bit-vec-0_6"]} rust-postgres = { package = "tokio-postgres", git = "https://github.com/imor/rust-postgres", rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b"} +rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/imor/rust-postgres", features = ["runtime"], rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b" } bit-vec = "=0.6.3" mappable-rc = "^0" mysql_async = { version = "*", default-features = false, features = ["minimal", "default", "native-tls-tls", "rust_decimal"]} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 9f385d12b5..6b6909ab7b 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -0c89b8974ff6e1c9eda2134f09d4a03f18b57c15 +7cc103e5827b1ec91180a5939a9c022a2a1856c6 \ No newline at end of file diff --git a/backend/migrations/20250122211942_operator_settings.down.sql b/backend/migrations/20250122211942_operator_settings.down.sql new file mode 100644 index 0000000000..4831bcdf10 --- /dev/null +++ b/backend/migrations/20250122211942_operator_settings.down.sql @@ -0,0 +1 @@ +ALTER TABLE workspace_settings DROP COLUMN operator_settings; \ No newline at end of file diff --git a/backend/migrations/20250122211942_operator_settings.up.sql b/backend/migrations/20250122211942_operator_settings.up.sql new file mode 100644 index 0000000000..22bbddb17c --- /dev/null +++ b/backend/migrations/20250122211942_operator_settings.up.sql @@ -0,0 +1,11 @@ +ALTER TABLE workspace_settings ADD COLUMN operator_settings JSONB DEFAULT '{ + "runs": true, + "groups": true, + "folders": true, + "workers": true, + "triggers": true, + "resources": true, + "schedules": true, + "variables": true, + "audit_logs": true +}'; diff --git a/backend/migrations/20250128154615_team_last_warning_sent.down.sql b/backend/migrations/20250128154615_team_last_warning_sent.down.sql new file mode 100644 index 0000000000..76e5671782 --- /dev/null +++ b/backend/migrations/20250128154615_team_last_warning_sent.down.sql @@ -0,0 +1 @@ +ALTER TABLE cloud_workspace_settings DROP COLUMN last_warning_sent; \ No newline at end of file diff --git a/backend/migrations/20250128154615_team_last_warning_sent.up.sql b/backend/migrations/20250128154615_team_last_warning_sent.up.sql new file mode 100644 index 0000000000..8b5cbd1c8b --- /dev/null +++ b/backend/migrations/20250128154615_team_last_warning_sent.up.sql @@ -0,0 +1 @@ +ALTER TABLE cloud_workspace_settings ADD COLUMN last_warning_sent TIMESTAMP NULL; \ No newline at end of file diff --git a/backend/migrations/20250128162249_addback_oracledb.down.sql b/backend/migrations/20250128162249_addback_oracledb.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250128162249_addback_oracledb.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250128162249_addback_oracledb.up.sql b/backend/migrations/20250128162249_addback_oracledb.up.sql new file mode 100644 index 0000000000..c25f8568a6 --- /dev/null +++ b/backend/migrations/20250128162249_addback_oracledb.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["oracledb"]'::jsonb) where name = 'worker__native' and config @> '{"worker_tags": ["nativets", "postgresql", "mysql", "graphql", "snowflake", "bigquery", "mssql"]}'::jsonb AND NOT config->'worker_tags' @> '"oracledb"'::jsonb; diff --git a/backend/migrations/20250129094837_websocket_can_return_message.down.sql b/backend/migrations/20250129094837_websocket_can_return_message.down.sql new file mode 100644 index 0000000000..02cd2ce8c2 --- /dev/null +++ b/backend/migrations/20250129094837_websocket_can_return_message.down.sql @@ -0,0 +1 @@ +ALTER TABLE websocket_trigger DROP COLUMN can_return_message; \ No newline at end of file diff --git a/backend/migrations/20250129094837_websocket_can_return_message.up.sql b/backend/migrations/20250129094837_websocket_can_return_message.up.sql new file mode 100644 index 0000000000..3f2a0b17a0 --- /dev/null +++ b/backend/migrations/20250129094837_websocket_can_return_message.up.sql @@ -0,0 +1 @@ +ALTER TABLE websocket_trigger ADD COLUMN can_return_message BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/backend/parsers/windmill-parser-py-imports/src/lib.rs b/backend/parsers/windmill-parser-py-imports/src/lib.rs index d0a5c9622b..58f857ef08 100644 --- a/backend/parsers/windmill-parser-py-imports/src/lib.rs +++ b/backend/parsers/windmill-parser-py-imports/src/lib.rs @@ -37,6 +37,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma "smb" => "pysmb", "PIL" => "Pillow", "googleapiclient" => "google-api-python-client", + "googlecloudbigquery" => "google-cloud-bigquery", "dateutil" => "python-dateutil", "mailparser" => "mail-parser", "mailparser-reply" => "mail-parser-reply", diff --git a/backend/parsers/windmill-parser-wasm/flake.lock b/backend/parsers/windmill-parser-wasm/flake.lock new file mode 100644 index 0000000000..c8733c1921 --- /dev/null +++ b/backend/parsers/windmill-parser-wasm/flake.lock @@ -0,0 +1,95 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1737885589, + "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1736320768, + "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1738117527, + "narHash": "sha256-GFviGfaezjGLFUlxdv3zyC7rSZvTXqwcG/YsF6MDkOw=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "6a3dc6ce4132bd57359214d986db376f2333c14d", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/backend/parsers/windmill-parser-wasm/flake.nix b/backend/parsers/windmill-parser-wasm/flake.nix new file mode 100644 index 0000000000..ac97d8c80a --- /dev/null +++ b/backend/parsers/windmill-parser-wasm/flake.nix @@ -0,0 +1,37 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay.url = "github:oxalica/rust-overlay"; + }; + + outputs = { + nixpkgs, + flake-utils, + rust-overlay, + ... + }: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { + inherit system; + overlays = [(import rust-overlay)]; + }; + rust = pkgs.rust-bin.nightly.latest.default.override { + extensions = [ + "rust-src" + ]; + targets = ["wasm32-unknown-unknown"]; + }; + in { + devShell = pkgs.mkShell { + buildInputs = with pkgs; [ + rust + nodejs + wasm-pack + sccache + ]; + RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache"; + CARGO_PATH = "${rust}/bin/cargo"; + }; + }); +} diff --git a/backend/src/main.rs b/backend/src/main.rs index fca33c9b82..3e10f03a05 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -36,12 +36,13 @@ use windmill_common::{ CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING, - INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, - LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING, - NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING, - REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, - RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, - TIMEOUT_WAIT_RESULT_SETTING, TEAMS_SETTING + INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, + KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, + NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, + PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, + REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, + SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING, + TIMEOUT_WAIT_RESULT_SETTING, }, scripts::ScriptLang, stats_ee::schedule_stats, @@ -788,7 +789,7 @@ Windmill Community Edition {GIT_VERSION} EXPOSE_METRICS_SETTING => { tracing::info!("Metrics setting changed, restarting"); // we wait a bit randomly to avoid having all servers and workers shutdown at same time - let rd_delay = rand::thread_rng().gen_range(0..40); + let rd_delay = rand::rng().random_range(0..40); tokio::time::sleep(Duration::from_secs(rd_delay)).await; if let Err(e) = tx.send(()) { tracing::error!(error = %e, "Could not send killpill to server"); @@ -802,7 +803,7 @@ Windmill Community Edition {GIT_VERSION} OTEL_SETTING => { tracing::info!("OTEL setting changed, restarting"); // we wait a bit randomly to avoid having all servers and workers shutdown at same time - let rd_delay = rand::thread_rng().gen_range(0..4); + let rd_delay = rand::rng().random_range(0..4); tokio::time::sleep(Duration::from_secs(rd_delay)).await; if let Err(e) = tx.send(()) { tracing::error!(error = %e, "Could not send killpill"); @@ -812,7 +813,7 @@ Windmill Community Edition {GIT_VERSION} if server_mode { tracing::info!("Request limit size change detected, killing server expecting to be restarted"); // we wait a bit randomly to avoid having all servers shutdown at same time - let rd_delay = rand::thread_rng().gen_range(0..4); + let rd_delay = rand::rng().random_range(0..4); tokio::time::sleep(Duration::from_secs(rd_delay)).await; if let Err(e) = tx.send(()) { tracing::error!(error = %e, "Could not send killpill to server"); diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 587a25d64a..33488c6356 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -1506,9 +1506,20 @@ pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> { async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker_name: &str) { if *RESTART_ZOMBIE_JOBS { let restarted = sqlx::query!( - "UPDATE queue SET running = false, started_at = null - WHERE last_ping < now() - ($1 || ' seconds')::interval - AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false RETURNING id, workspace_id, last_ping", + "WITH zombie_jobs AS ( + UPDATE queue SET running = false, started_at = null + WHERE last_ping < now() - ($1 || ' seconds')::interval + AND running = true AND job_kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlescriptflow') AND same_worker = false + RETURNING id, workspace_id, last_ping + ), + update_concurrency AS ( + UPDATE concurrency_counter cc + SET job_uuids = job_uuids - zj.id::text + FROM zombie_jobs zj + INNER JOIN concurrency_key ck ON ck.job_id = zj.id + WHERE cc.concurrency_id = ck.key + ) + SELECT id, workspace_id, last_ping FROM zombie_jobs", *ZOMBIE_JOB_TIMEOUT, ) .fetch_all(db) @@ -1652,12 +1663,31 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> { tracing::error!(error_message); report_critical_error(error_message, db.clone(), Some(&flow.workspace_id), None).await; // if the flow hasn't started and is a zombie, we can simply restart it + let mut tx = db.begin().await?; + + let concurrency_key = + sqlx::query_scalar!("SELECT key FROM concurrency_key WHERE job_id = $1", flow.id) + .fetch_optional(&mut *tx) + .await?; + + if let Some(key) = concurrency_key { + sqlx::query!( + "UPDATE concurrency_counter SET job_uuids = job_uuids - $2 WHERE concurrency_id = $1", + key, + flow.id.hyphenated().to_string() + ) + .execute(&mut *tx) + .await?; + } + sqlx::query!( "UPDATE queue SET running = false, started_at = null WHERE id = $1 AND canceled = false", flow.id ) - .execute(db) + .execute(&mut *tx) .await?; + + tx.commit().await?; } else { let id = flow.id.clone(); let last_ping = flow.last_ping.clone(); diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index 2033132b47..cbecf865de 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -28,7 +28,7 @@ zip = ["dep:async_zip"] oauth2 = ["dep:async-oauth2"] http_trigger = ["dep:matchit"] static_frontend = ["dep:rust-embed"] -postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal"] +postgres_trigger = ["dep:rust-postgres", "dep:pg_escape", "dep:byteorder", "dep:thiserror", "dep:rust_decimal", "dep:rust-postgres-native-tls"] [dependencies] windmill-queue.workspace = true @@ -116,4 +116,5 @@ rust-postgres = { workspace = true, optional = true } pg_escape = { workspace = true, optional = true } byteorder = { workspace = true, optional = true } thiserror = { workspace = true, optional = true } -rust_decimal = { workspace = true, optional = true } \ No newline at end of file +rust_decimal = { workspace = true, optional = true } +rust-postgres-native-tls = { workspace = true, optional = true} \ No newline at end of file diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 82bd7a9c0e..83d5f366a9 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.448.1 + version: 1.454.1 title: Windmill API contact: @@ -1618,6 +1618,29 @@ paths: schema: $ref: "#/components/schemas/User" + /w/{workspace}/workspaces/operator_settings: + post: + operationId: updateOperatorSettings + summary: Update operator settings for a workspace + description: Updates the operator settings for a specific workspace. Requires workspace admin privileges. + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/OperatorSettings" + responses: + '200': + description: Operator settings updated successfully + content: + text/plain: + schema: + type: string + /users/exists/{email}: get: summary: exists email @@ -1745,6 +1768,8 @@ paths: type: boolean color: type: string + operator_settings: + $ref: "#/components/schemas/OperatorSettings" required: - code_completion_enabled - automatic_billing @@ -7918,6 +7943,39 @@ paths: schema: type: string + /w/{workspace}/websocket_triggers/test: + post: + summary: test websocket connection + operationId: testWebsocketConnection + tags: + - websocket_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: test websocket connection + required: true + content: + application/json: + schema: + type: object + properties: + url: + type: string + url_runnable_args: + $ref: "#/components/schemas/ScriptArgs" + can_return_message: + type: boolean + required: + - url + - can_return_message + responses: + "200": + description: successfuly connected to websocket + content: + text/plain: + schema: + type: string + /w/{workspace}/kafka_triggers/create: post: summary: create kafka trigger @@ -8079,6 +8137,34 @@ paths: schema: type: string + /w/{workspace}/kafka_triggers/test: + post: + summary: test kafka connection + operationId: testKafkaConnection + tags: + - kafka_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: test kafka connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + "200": + description: successfuly connected to kafka brokers + content: + text/plain: + schema: + type: string + /w/{workspace}/nats_triggers/create: post: summary: create nats trigger @@ -8242,6 +8328,35 @@ paths: schema: type: string + + /w/{workspace}/nats_triggers/test: + post: + summary: test NATS connection + operationId: testNatsConnection + tags: + - nats_trigger + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: test nats connection + required: true + content: + application/json: + schema: + type: object + properties: + connection: + type: object + required: + - connection + responses: + "200": + description: successfuly connected to NATS servers + content: + text/plain: + schema: + type: string + /w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}: get: summary: check if postgres configuration is set to logical @@ -9712,6 +9827,25 @@ paths: $ref: "#/components/schemas/Capture" /w/{workspace}/capture/{id}: + get: + summary: get a capture + operationId: getCapture + tags: + - capture + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: id + in: path + required: true + schema: + type: integer + responses: + "200": + description: capture + content: + application/json: + schema: + $ref: "#/components/schemas/Capture" delete: summary: delete a capture operationId: deleteCapture @@ -9980,6 +10114,8 @@ paths: properties: connection_settings_str: type: string + azure_container_path: + type: string required: - connection_settings_str @@ -13008,6 +13144,8 @@ components: $ref: "#/components/schemas/WebsocketTriggerInitialMessage" url_runnable_args: $ref: "#/components/schemas/ScriptArgs" + can_return_message: + type: boolean required: - path @@ -13021,6 +13159,7 @@ components: - workspace_id - enabled - filters + - can_return_message NewWebsocketTrigger: type: object @@ -13052,6 +13191,8 @@ components: $ref: "#/components/schemas/WebsocketTriggerInitialMessage" url_runnable_args: $ref: "#/components/schemas/ScriptArgs" + can_return_message: + type: boolean required: - path @@ -13059,6 +13200,7 @@ components: - url - is_flow - filters + - can_return_message EditWebsocketTrigger: type: object @@ -13088,6 +13230,8 @@ components: $ref: "#/components/schemas/WebsocketTriggerInitialMessage" url_runnable_args: $ref: "#/components/schemas/ScriptArgs" + can_return_message: + type: boolean required: - path @@ -13095,7 +13239,7 @@ components: - url - is_flow - filters - + - can_return_message WebsocketTriggerInitialMessage: anyOf: - type: object @@ -13221,6 +13365,9 @@ components: type: string error: type: string + last_server_ping: + type: string + format: date-time required: - path - script_path @@ -13640,6 +13787,8 @@ components: type: string color: type: string + operator_settings: + $ref: "#/components/schemas/OperatorSettings" required: - id - name @@ -14498,6 +14647,48 @@ components: required: - trigger_kind + OperatorSettings: + nullable: true + type: object + required: + - runs + - schedules + - resources + - variables + - triggers + - audit_logs + - groups + - folders + - workers + properties: + runs: + type: boolean + description: Whether operators can view runs + schedules: + type: boolean + description: Whether operators can view schedules + resources: + type: boolean + description: Whether operators can view resources + variables: + type: boolean + description: Whether operators can view variables + audit_logs: + type: boolean + description: Whether operators can view audit logs + triggers: + type: boolean + description: Whether operators can view triggers + groups: + type: boolean + description: Whether operators can view groups page + folders: + type: boolean + description: Whether operators can view folders page + workers: + type: boolean + description: Whether operators can view workers page + TeamInfo: type: object required: @@ -14518,7 +14709,7 @@ components: description: List of channels within the team items: $ref: '#/components/schemas/ChannelInfo' - + ChannelInfo: type: object required: diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index f2e340394e..bf134adc7f 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -16,7 +16,6 @@ use crate::{ webhook_util::{WebhookMessage, WebhookShared}, HTTP_CLIENT, }; -use windmill_common::variables::encrypt; #[cfg(feature = "parquet")] use crate::{ job_helpers_ee::{ @@ -54,6 +53,7 @@ use windmill_audit::audit_ee::audit_log; use windmill_audit::ActionKind; #[cfg(feature = "parquet")] use windmill_common::s3_helpers::build_object_store_client; +use windmill_common::variables::encrypt; use windmill_common::{ apps::{AppScriptId, ListAppQuery}, cache::{self, future::FutureCachedExt}, diff --git a/backend/windmill-api/src/capture.rs b/backend/windmill-api/src/capture.rs index 323cb6d67e..fe752336f3 100644 --- a/backend/windmill-api/src/capture.rs +++ b/backend/windmill-api/src/capture.rs @@ -35,9 +35,9 @@ use windmill_queue::{PushArgs, PushArgsOwned}; #[cfg(feature = "http_trigger")] use crate::http_triggers::{build_http_trigger_extra, HttpMethod}; #[cfg(all(feature = "enterprise", feature = "kafka"))] -use crate::kafka_triggers_ee::KafkaResourceSecurity; +use crate::kafka_triggers_ee::KafkaTriggerConfigConnection; #[cfg(all(feature = "enterprise", feature = "nats"))] -use crate::nats_triggers_ee::NatsResourceAuth; +use crate::nats_triggers_ee::NatsTriggerConfigConnection; use crate::{ args::WebhookArgs, db::{ApiAuthed, DB}, @@ -56,6 +56,7 @@ pub fn workspaced_service() -> Router { .route("/get_configs/:runnable_kind/*path", get(get_configs)) .route("/list/:runnable_kind/*path", get(list_captures)) .route("/:id", delete(delete_capture)) + .route("/:id", get(get_capture)) } pub fn workspaced_unauthed_service() -> Router { @@ -110,14 +111,6 @@ struct HttpTriggerConfig { http_method: HttpMethod, } -#[cfg(all(feature = "enterprise", feature = "kafka"))] -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum KafkaTriggerConfigConnection { - Resource { kafka_resource_path: String }, - Static { brokers: Vec, security: KafkaResourceSecurity }, -} - #[cfg(all(feature = "enterprise", feature = "kafka"))] #[derive(Serialize, Deserialize)] pub struct KafkaTriggerConfig { @@ -127,14 +120,6 @@ pub struct KafkaTriggerConfig { pub group_id: String, } -#[cfg(all(feature = "enterprise", feature = "nats"))] -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum NatsTriggerConfigConnection { - Resource { nats_resource_path: String }, - Static { servers: Vec, auth: NatsResourceAuth, require_tls: bool }, -} - #[cfg(all(feature = "enterprise", feature = "nats"))] #[derive(Serialize, Deserialize)] pub struct NatsTriggerConfig { @@ -296,7 +281,7 @@ async fn list_captures( let captures = sqlx::query_as!( Capture, - r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload: _", trigger_extra as "trigger_extra: _" + r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", CASE WHEN pg_column_size(payload) < 40000 THEN payload ELSE '"WINDMILL_TOO_BIG"'::jsonb END as "payload!: _", trigger_extra as "trigger_extra: _" FROM capture WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 @@ -319,6 +304,24 @@ async fn list_captures( Ok(Json(captures)) } +async fn get_capture( + authed: ApiAuthed, + Extension(user_db): Extension, + Path((w_id, id)): Path<(String, i64)>, +) -> JsonResult { + let mut tx = user_db.begin(&authed).await?; + let capture = sqlx::query_as!( + Capture, + r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload!: _", trigger_extra as "trigger_extra: _" FROM capture WHERE id = $1 AND workspace_id = $2"#, + id, + &w_id, + ) + .fetch_one(&mut *tx) + .await?; + tx.commit().await?; + Ok(Json(capture)) +} + async fn delete_capture( authed: ApiAuthed, Extension(user_db): Extension, diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index 00a2cd915c..ef62928f2c 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -521,7 +521,7 @@ async fn get_http_route_trigger( trigger.email.clone(), &trigger.workspace_id, &db, - Some(username_override.unwrap_or("anonymous".to_string())), + Some(username_override.unwrap_or(format!("http-{}", trigger.path))), ) .await?; @@ -685,12 +685,6 @@ async fn route_job( .await, ); - let label_prefix = Some(format!( - "http-{}-{}-", - method.as_str().to_lowercase(), - trigger.route_path - )); - let run_query = RunJobQuery::default(); if trigger.is_flow { @@ -703,7 +697,7 @@ async fn route_job( StripPath(trigger.script_path.to_owned()), run_query, args, - label_prefix, + None, ) .await .into_response() @@ -716,7 +710,7 @@ async fn route_job( user_db, args, trigger.workspace_id.clone(), - label_prefix, + None, ) .await .into_response() @@ -731,7 +725,7 @@ async fn route_job( StripPath(trigger.script_path.to_owned()), run_query, args, - label_prefix, + None, ) .await .into_response() @@ -744,7 +738,7 @@ async fn route_job( user_db, trigger.workspace_id.clone(), args, - label_prefix, + None, ) .await .into_response() diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 6e88f76ca7..f039c220fb 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -9,12 +9,14 @@ use axum::body::Body; use axum::http::HeaderValue; use futures::TryFutureExt; +use http::{HeaderMap, HeaderName}; use itertools::Itertools; use quick_cache::sync::Cache; use serde_json::value::RawValue; use sqlx::Pool; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; +use std::str::FromStr; #[cfg(feature = "prometheus")] use std::sync::atomic::Ordering; use tokio::io::AsyncReadExt; @@ -1966,20 +1968,34 @@ async fn resume_suspended_job_internal( resume_immediately_if_relevant(parent_flow_info, job_id, &mut tx).await?; } + let approver = approver.unwrap_or_else(|| "anonymous".to_string()); + let audit_author = match authed { Some(authed) => (&authed).into(), - None => { - let approver = approver.unwrap_or_else(|| "anonymous".to_string()); - AuditAuthor { email: approver.clone(), username: approver, username_override: None } - } + None => AuditAuthor { + email: approver.clone(), + username: approver.clone(), + username_override: None, + }, }; audit_log( &mut *tx, &audit_author, - "jobs.approved", + "jobs.suspend_resume", ActionKind::Update, &w_id, - Some(&job_id.to_string()), + Some( + &serde_json::json!({ + "approved": approved, + "job_id": job_id, + "details": if approved { + format!("Approved by {}", &approver) + } else { + format!("Cancelled by {}", &approver) + } + }) + .to_string(), + ), None, ) .await?; @@ -3522,15 +3538,17 @@ lazy_static::lazy_static! { pub struct WindmillCompositeResult { windmill_status_code: Option, windmill_content_type: Option, + windmill_headers: Option>, result: Option>, } -pub async fn run_wait_result( + +pub async fn run_wait_result_internal( db: &DB, uuid: Uuid, w_id: String, node_id_for_empty_return: Option, username: &str, -) -> error::Result { +) -> error::Result<(Box, bool)> { let mut result = None; let mut success = false; let timeout = TIMEOUT_WAIT_RESULT.read().await.clone().unwrap_or(600); @@ -3602,81 +3620,110 @@ pub async fn run_wait_result( }; tokio::time::sleep(core::time::Duration::from_millis(delay)).await; } + if let Some(result) = result { g.done = true; + Ok((result, success)) + } else { + Err(Error::ExecutionErr(format!("timeout after {}s", timeout))) + } +} - let composite_result = serde_json::from_str::(result.get()); - match composite_result { - Ok(WindmillCompositeResult { - windmill_status_code, - windmill_content_type, - result: result_value, - }) => { - if windmill_content_type.is_none() && windmill_status_code.is_none() { - return Ok(( - if success { - StatusCode::OK - } else { - StatusCode::INTERNAL_SERVER_ERROR - }, - Json(result), - ) - .into_response()); - } +pub async fn run_wait_result( + db: &DB, + uuid: Uuid, + w_id: String, + node_id_for_empty_return: Option, + username: &str, +) -> error::Result { + let (result, success) = + run_wait_result_internal(db, uuid, w_id, node_id_for_empty_return, username).await?; - let status_code_or_default = windmill_status_code - .map(|val| match StatusCode::from_u16(val) { - Ok(sc) => Ok(sc), - Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())), - }) - .unwrap_or_else(|| { - if !success { - Ok(StatusCode::INTERNAL_SERVER_ERROR) - } else if result_value.is_some() { - Ok(StatusCode::OK) - } else { - Ok(StatusCode::NO_CONTENT) - } - })?; - - if windmill_content_type.is_some() { - let serialized_json_result = result_value - .map(|val| val.get().to_owned()) - .unwrap_or_else(String::new); - // if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string. - // it falls back to the original serialized JSON if it doesn't work. - let serialized_result = - serde_json::from_str::(serialized_json_result.as_str()) - .ok() - .unwrap_or(serialized_json_result); - return Ok(( - status_code_or_default, - [( - http::header::CONTENT_TYPE, - HeaderValue::from_str(windmill_content_type.unwrap().as_str()).unwrap(), - )], - serialized_result, - ) - .into_response()); - } + let composite_result = serde_json::from_str::(result.get()); + match composite_result { + Ok(WindmillCompositeResult { + windmill_status_code, + windmill_content_type, + windmill_headers, + result: result_value, + }) => { + if windmill_content_type.is_none() + && windmill_status_code.is_none() + && windmill_headers.is_none() + { return Ok(( - status_code_or_default, - Json(result_value), // default to JSON result if no content type is provided + if success { + StatusCode::OK + } else { + StatusCode::INTERNAL_SERVER_ERROR + }, + Json(result), ) .into_response()); } - _ => Ok(( - if success { - StatusCode::OK - } else { - StatusCode::INTERNAL_SERVER_ERROR - }, - Json(result), - ) - .into_response()), + + let status_code_or_default = windmill_status_code + .map(|val| match StatusCode::from_u16(val) { + Ok(sc) => Ok(sc), + Err(_) => Err(Error::ExecutionErr("Invalid status code".to_string())), + }) + .unwrap_or_else(|| { + if !success { + Ok(StatusCode::INTERNAL_SERVER_ERROR) + } else if result_value.is_some() { + Ok(StatusCode::OK) + } else { + Ok(StatusCode::NO_CONTENT) + } + })?; + + let mut headers = HeaderMap::new(); + + if let Some(windmill_headers) = windmill_headers { + for (k, v) in windmill_headers { + let k = HeaderName::from_str(k.as_str()).map_err(|err| { + Error::InternalErr(format!("Invalid header name {k}: {err}")) + })?; + let v = HeaderValue::from_str(v.as_str()).map_err(|err| { + Error::InternalErr(format!("Invalid header value {v}: {err}")) + })?; + headers.insert(k, v); + } + } + + if let Some(content_type) = windmill_content_type { + let serialized_json_result = result_value + .map(|val| val.get().to_owned()) + .unwrap_or_else(String::new); + // if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string. + // it falls back to the original serialized JSON if it doesn't work. + let serialized_result = + serde_json::from_str::(serialized_json_result.as_str()) + .ok() + .unwrap_or(serialized_json_result); + headers.insert( + http::header::CONTENT_TYPE, + HeaderValue::from_str(content_type.as_str()).map_err(|err| { + Error::InternalErr(format!("Invalid content type {content_type}: {err}")) + })?, + ); + return Ok((status_code_or_default, headers, serialized_result).into_response()); + } + if let Some(result_value) = result_value { + return Ok((status_code_or_default, headers, Json(result_value)).into_response()); + } else { + Ok((status_code_or_default, headers).into_response()) + } } - } else { - Err(Error::ExecutionErr(format!("timeout after {}s", timeout))) + _ => Ok(( + if success { + StatusCode::OK + } else { + StatusCode::INTERNAL_SERVER_ERROR + }, + Json(result), + ) + .into_response()), } } diff --git a/backend/windmill-api/src/kafka_triggers_ee.rs b/backend/windmill-api/src/kafka_triggers_ee.rs index e3de5e6042..d02ea970e3 100644 --- a/backend/windmill-api/src/kafka_triggers_ee.rs +++ b/backend/windmill-api/src/kafka_triggers_ee.rs @@ -9,9 +9,12 @@ pub fn workspaced_service() -> Router { Router::new() } -pub async fn start_kafka_consumers( +pub fn start_kafka_consumers( _db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, ) -> () { // implementation is not open source } + +#[derive(Serialize, Deserialize)] +pub enum KafkaTriggerConfigConnection {} diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 006e52f950..36552f9a57 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -59,8 +59,6 @@ mod auth; mod capture; mod concurrency_groups; mod configs; -#[cfg(feature = "postgres_trigger")] -mod postgres_triggers; mod db; mod drafts; pub mod ee; @@ -75,6 +73,8 @@ mod http_triggers; mod indexer_ee; mod inputs; mod integration; +#[cfg(feature = "postgres_trigger")] +mod postgres_triggers; #[cfg(feature = "enterprise")] mod apps_ee; @@ -98,12 +98,12 @@ mod scripts; mod service_logs; mod settings; mod slack_approvals; -#[cfg(feature = "enterprise")] -mod teams_ee; #[cfg(feature = "smtp")] mod smtp_server_ee; mod static_assets; mod stripe_ee; +#[cfg(feature = "enterprise")] +mod teams_ee; mod tracing_init; mod triggers; mod users; @@ -296,24 +296,24 @@ pub async fn run_server( #[cfg(feature = "websocket")] { let ws_killpill_rx = rx.resubscribe(); - websocket_triggers::start_websockets(db.clone(), ws_killpill_rx).await; + websocket_triggers::start_websockets(db.clone(), ws_killpill_rx); } #[cfg(all(feature = "enterprise", feature = "kafka"))] { let kafka_killpill_rx = rx.resubscribe(); - kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx).await; + kafka_triggers_ee::start_kafka_consumers(db.clone(), kafka_killpill_rx); } #[cfg(all(feature = "enterprise", feature = "nats"))] { let nats_killpill_rx = rx.resubscribe(); - nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx).await; + nats_triggers_ee::start_nats_consumers(db.clone(), nats_killpill_rx); } #[cfg(feature = "postgres_trigger")] { let db_killpill_rx = rx.resubscribe(); - postgres_triggers::start_database(db.clone(), db_killpill_rx).await; + postgres_triggers::start_database(db.clone(), db_killpill_rx); } } @@ -454,7 +454,7 @@ pub async fn run_server( ) .route("/slack", post(slack_approvals::slack_app_callback_handler)) .nest("/teams", { - #[cfg(feature = "enterprise")] + #[cfg(feature = "enterprise")] { teams_ee::teams_service() } diff --git a/backend/windmill-api/src/nats_triggers_ee.rs b/backend/windmill-api/src/nats_triggers_ee.rs index fedf7981ff..bfa5fbdd73 100644 --- a/backend/windmill-api/src/nats_triggers_ee.rs +++ b/backend/windmill-api/src/nats_triggers_ee.rs @@ -9,9 +9,9 @@ pub fn workspaced_service() -> Router { Router::new() } -pub async fn start_nats_consumers( - _db: DB, - mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, -) -> () { +pub fn start_nats_consumers(_db: DB, mut _killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () { // implementation is not open source } + +#[derive(Serialize, Deserialize)] +pub enum NatsTriggerConfigConnection {} diff --git a/backend/windmill-api/src/postgres_triggers/handler.rs b/backend/windmill-api/src/postgres_triggers/handler.rs index fc6adbb6e2..5bcfb69acc 100644 --- a/backend/windmill-api/src/postgres_triggers/handler.rs +++ b/backend/windmill-api/src/postgres_triggers/handler.rs @@ -46,6 +46,7 @@ pub struct Database { pub host: String, pub port: u16, pub dbname: String, + #[serde(default)] pub sslmode: String, pub root_certificate_pem: String, } @@ -251,6 +252,12 @@ pub async fn create_postgres_trigger( Path(w_id): Path, Json(new_postgres_trigger): Json, ) -> error::Result<(StatusCode, String)> { + if *CLOUD_HOSTED { + return Err(error::Error::BadRequest( + "Postgres triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(), + )); + } + let NewPostgresTrigger { postgres_resource_path, path, @@ -262,12 +269,6 @@ pub async fn create_postgres_trigger( publication, } = new_postgres_trigger; - if *CLOUD_HOSTED { - return Err(error::Error::BadRequest( - "Postgres triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(), - )); - } - if publication_name.is_none() && publication.is_none() { return Err(error::Error::BadRequest( "Publication data is missing".to_string(), @@ -283,14 +284,14 @@ pub async fn create_postgres_trigger( if create_publication || create_slot { let generate_random_string = move || { let timestamp = Utc::now().timestamp_millis().to_string(); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let charset = "abcdefghijklmnopqrstuvwxyz0123456789"; let random_part = (0..10) .map(|_| { charset .chars() - .nth(rng.gen_range(0..charset.len())) + .nth(rng.random_range(0..charset.len())) .unwrap() }) .collect::(); diff --git a/backend/windmill-api/src/postgres_triggers/mod.rs b/backend/windmill-api/src/postgres_triggers/mod.rs index 9aa50f8fec..0bd87ed262 100644 --- a/backend/windmill-api/src/postgres_triggers/mod.rs +++ b/backend/windmill-api/src/postgres_triggers/mod.rs @@ -110,14 +110,13 @@ async fn run_job( trigger: &PostgresTrigger, ) -> anyhow::Result<()> { let args = PushArgsOwned { args: args.unwrap_or_default(), extra }; - let label_prefix = Some(format!("db-{}-", trigger.path)); let authed = fetch_api_authed( trigger.edited_by.clone(), trigger.email.clone(), &trigger.workspace_id, db, - Some("anonymous".to_string()), + Some(format!("postgres-{}", trigger.path)), ) .await?; @@ -134,7 +133,7 @@ async fn run_job( StripPath(trigger.script_path.to_owned()), run_query, args, - label_prefix, + None, ) .await?; } else { @@ -146,7 +145,7 @@ async fn run_job( StripPath(trigger.script_path.to_owned()), run_query, args, - label_prefix, + None, ) .await?; } diff --git a/backend/windmill-api/src/postgres_triggers/trigger.rs b/backend/windmill-api/src/postgres_triggers/trigger.rs index 0ed9891cbc..ea0f9a5dff 100644 --- a/backend/windmill-api/src/postgres_triggers/trigger.rs +++ b/backend/windmill-api/src/postgres_triggers/trigger.rs @@ -16,9 +16,11 @@ use crate::{ use bytes::{BufMut, Bytes, BytesMut}; use chrono::TimeZone; use futures::{pin_mut, SinkExt, StreamExt}; +use native_tls::TlsConnector; use pg_escape::{quote_identifier, quote_literal}; use rand::seq::SliceRandom; -use rust_postgres::{Client, Config, CopyBothDuplex, NoTls, SimpleQueryMessage}; +use rust_postgres::{config::SslMode, Client, Config, CopyBothDuplex, SimpleQueryMessage}; +use rust_postgres_native_tls::MakeTlsConnector; use windmill_common::{ db::UserDB, utils::report_critical_error, worker::to_raw_value, INSTANCE_NAME, }; @@ -60,28 +62,50 @@ impl RowExist for Vec { #[derive(thiserror::Error, Debug)] enum Error { #[error("Error from database: {0}")] - Postgres(rust_postgres::Error), + Postgres(#[from] rust_postgres::Error), #[error("Error : {0}")] - Common(windmill_common::error::Error), + Common(#[from] windmill_common::error::Error), + #[error("Tls Error: {0}")] + Tls(#[from] native_tls::Error), } pub struct PostgresSimpleClient(Client); impl PostgresSimpleClient { async fn new(database: &Database) -> Result { + let ssl_mode = match database.sslmode.as_ref() { + "disable" => SslMode::Disable, + "" | "prefer" | "allow" => SslMode::Prefer, + "require" => SslMode::Require, + "verify-ca" => SslMode::VerifyCa, + "verify-full" => SslMode::VerifyFull, + ssl_mode => { + return Err(Error::Common(windmill_common::error::Error::BadRequest( + format!("Invalid ssl mode for postgres: {}, please put a valid ssl_mode among the following avalible ssl mode: ['disable', 'allow', 'prefer', 'verify-ca', 'verify-full']", ssl_mode), + ))) + } + }; + let mut config = Config::new(); config .dbname(&database.dbname) .host(&database.host) .port(database.port) .user(&database.user) + .ssl_mode(ssl_mode) .replication_mode(rust_postgres::config::ReplicationMode::Logical); if !database.password.is_empty() { config.password(&database.password); } - let (client, connection) = config.connect(NoTls).await.map_err(Error::Postgres)?; + if !database.root_certificate_pem.is_empty() { + config.ssl_root_cert(database.root_certificate_pem.as_bytes()); + } + + let connector = MakeTlsConnector::new(TlsConnector::new()?); + + let (client, connection) = config.connect(connector).await?; tokio::spawn(async move { if let Err(e) = connection.await { tracing::debug!("{:#?}", e); @@ -111,8 +135,7 @@ impl PostgresSimpleClient { Ok(( self.0 .copy_both_simple::(query.as_str()) - .await - .map_err(Error::Postgres)?, + .await?, LogicalReplicationSettings::new(false), )) } @@ -250,8 +273,7 @@ async fn listen_to_transactions( &db, None, ) - .await - .map_err(Error::Common)?; + .await?; let database = get_database_resource( authed, @@ -260,8 +282,7 @@ async fn listen_to_transactions( &postgres_trigger.postgres_resource_path, &postgres_trigger.workspace_id, ) - .await - .map_err(Error::Common)?; + .await?; let client = PostgresSimpleClient::new(&database).await?; @@ -274,7 +295,6 @@ async fn listen_to_transactions( Ok::<_, Error>((logical_replication_stream, logical_replication_settings)) }; - tokio::select! { biased; _ = killpill_rx.recv() => { @@ -305,7 +325,10 @@ async fn listen_to_transactions( let message = match message { Some(message) => message, None => { - tracing::info!("Stream for postgres trigger {} is empty, leaving....", postgres_trigger.path); + tracing::error!("Stream for postgres trigger {} closed", postgres_trigger.path); + if let None = update_ping(&db, postgres_trigger, Some("Stream closed")).await { + return; + } return; } }; @@ -359,7 +382,7 @@ async fn listen_to_transactions( Some((update.o_id, relations.body_to_json((update.o_id, update.new_tuple)), "update")) } Delete(delete) => { - let body = delete.old_tuple.unwrap_or(delete.key_tuple.unwrap()); + let body = delete.old_tuple.unwrap_or_else(|| delete.key_tuple.unwrap()); Some((delete.o_id, relations.body_to_json((delete.o_id, body)), "delete")) } }; @@ -389,7 +412,8 @@ async fn listen_to_transactions( } } Err(err) => { - tracing::error!("Postgres trigger error while trying to start_logical_replication_streaming: {}", err.to_string()) + tracing::error!("Postgres trigger error while trying to start logical replication streaming: {}", &err); + disable_with_error(&postgres_trigger, &db, err.to_string()).await } } } @@ -489,7 +513,7 @@ async fn listen_to_unlistened_database_events( match postgres_triggers { Ok(mut triggers) => { - triggers.shuffle(&mut rand::thread_rng()); + triggers.shuffle(&mut rand::rng()); for trigger in triggers { try_to_listen_to_database_transactions( trigger, @@ -505,7 +529,7 @@ async fn listen_to_unlistened_database_events( }; } -pub async fn start_database(db: DB, mut killpill_rx: tokio::sync::broadcast::Receiver<()>) { +pub fn start_database(db: DB, mut killpill_rx: tokio::sync::broadcast::Receiver<()>) { tokio::spawn(async move { listen_to_unlistened_database_events(&db, &killpill_rx).await; loop { diff --git a/backend/windmill-api/src/slack_approvals.rs b/backend/windmill-api/src/slack_approvals.rs index 9b02f4c73a..8d39131e7d 100644 --- a/backend/windmill-api/src/slack_approvals.rs +++ b/backend/windmill-api/src/slack_approvals.rs @@ -23,7 +23,7 @@ use windmill_common::{ error::{self, Error}, jobs::JobKind, scripts::ScriptHash, - variables::{build_crypt, decrypt_value_with_mc}, + variables::{build_crypt, decrypt}, }; #[derive(Deserialize, Debug)] @@ -853,7 +853,7 @@ async fn get_slack_token(db: &DB, slack_resource_path: &str, w_id: &str) -> anyh if slack_token.is_secret { let mc = build_crypt(&db, w_id).await?; - let bot_token = decrypt_value_with_mc(slack_token.value, mc).await?; + let bot_token = decrypt(&mc, slack_token.value)?; Ok(bot_token) } else { Ok(slack_token.value) diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index fdb40d97c5..b603259664 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -1718,6 +1718,7 @@ pub async fn create_session_token<'c>( if COOKIE_DOMAIN.is_some() { cookie.set_domain(COOKIE_DOMAIN.clone().unwrap()); } + let mut expire: OffsetDateTime = time::OffsetDateTime::now_utc(); expire += time::Duration::days(3); cookie.set_expires(expire); diff --git a/backend/windmill-api/src/websocket_triggers.rs b/backend/windmill-api/src/websocket_triggers.rs index d6260691c7..f90bb8def0 100644 --- a/backend/windmill-api/src/websocket_triggers.rs +++ b/backend/windmill-api/src/websocket_triggers.rs @@ -33,7 +33,9 @@ use windmill_queue::PushArgsOwned; use crate::{ capture::{insert_capture_payload, TriggerKind, WebsocketTriggerConfig}, db::{ApiAuthed, DB}, - jobs::{run_flow_by_path_inner, run_script_by_path_inner, RunJobQuery}, + jobs::{ + run_flow_by_path_inner, run_script_by_path_inner, run_wait_result_internal, RunJobQuery, + }, users::fetch_api_authed, }; @@ -48,6 +50,7 @@ pub fn workspaced_service() -> Router { .route("/delete/*path", delete(delete_websocket_trigger)) .route("/exists/*path", get(exists_websocket_trigger)) .route("/setenabled/*path", post(set_enabled)) + .route("/test", post(test_websocket_connection)) } #[derive(Deserialize)] @@ -60,6 +63,7 @@ struct NewWebsocketTrigger { filters: Vec>, initial_messages: Option>>, url_runnable_args: Option>, + can_return_message: bool, } #[derive(Deserialize)] @@ -100,6 +104,7 @@ pub struct WebsocketTrigger { filters: Vec>>, initial_messages: Option>>>, url_runnable_args: Option>>, + can_return_message: bool, } #[derive(Deserialize)] @@ -111,6 +116,7 @@ struct EditWebsocketTrigger { filters: Vec>, initial_messages: Option>>, url_runnable_args: Option>, + can_return_message: bool, } #[derive(Deserialize)] @@ -188,7 +194,7 @@ async fn create_websocket_trigger( ) -> error::Result<(StatusCode, String)> { if *CLOUD_HOSTED { return Err(error::Error::BadRequest( - "Websocket triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(), + "WebSocket triggers are not supported on multi-tenant cloud, use dedicated cloud or self-host".to_string(), )); } @@ -202,7 +208,7 @@ async fn create_websocket_trigger( .map(SqlxJson) .collect_vec(); sqlx::query_as::<_, WebsocketTrigger>( - "INSERT INTO websocket_trigger (workspace_id, path, url, script_path, is_flow, enabled, filters, initial_messages, url_runnable_args, edited_by, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now()) RETURNING *", + "INSERT INTO websocket_trigger (workspace_id, path, url, script_path, is_flow, enabled, filters, initial_messages, url_runnable_args, edited_by, can_return_message, email, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now()) RETURNING *", ) .bind(&w_id) .bind(&ct.path) @@ -214,6 +220,7 @@ async fn create_websocket_trigger( .bind(initial_messages.as_slice()) .bind(ct.url_runnable_args.map(SqlxJson)) .bind(&authed.username) + .bind(ct.can_return_message) .bind(&authed.email) .fetch_one(&mut *tx).await?; @@ -252,8 +259,8 @@ async fn update_websocket_trigger( // important to update server_id to NULL to stop current websocket listener sqlx::query!( - "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, edited_at = now(), server_id = NULL, error = NULL - WHERE workspace_id = $10 AND path = $11", + "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, can_return_message = $10, edited_at = now(), server_id = NULL, error = NULL + WHERE workspace_id = $11 AND path = $12", ct.url, ct.script_path, ct.path, @@ -263,6 +270,7 @@ async fn update_websocket_trigger( ct.url_runnable_args.map(SqlxJson) as Option>>, &authed.username, &authed.email, + ct.can_return_message, w_id, path, ) @@ -309,7 +317,7 @@ pub async fn set_enabled( w_id, ).fetch_optional(&mut *tx).await?; - not_found_if_none(one_o.flatten(), "Websocket trigger", path)?; + not_found_if_none(one_o.flatten(), "WebSocket trigger", path)?; audit_log( &mut *tx, @@ -325,7 +333,7 @@ pub async fn set_enabled( tx.commit().await?; Ok(format!( - "succesfully updated websocket trigger at path {} to status {}", + "succesfully updated WebSocket trigger at path {} to status {}", path, payload.enabled )) } @@ -358,7 +366,7 @@ async fn delete_websocket_trigger( tx.commit().await?; - Ok(format!("Websocket trigger {path} deleted")) + Ok(format!("WebSocket trigger {path} deleted")) } async fn exists_websocket_trigger( @@ -377,6 +385,64 @@ async fn exists_websocket_trigger( Ok(Json(exists)) } +#[derive(Debug, Deserialize)] +struct TestWebsocket { + url: String, + url_runnable_args: Option>, +} + +async fn test_websocket_connection( + authed: ApiAuthed, + Extension(db): Extension, + Path(workspace_id): Path, + Json(test_websocket): Json, +) -> error::Result<()> { + let url = test_websocket.url; + + let connect_f = async { + let connect_url: Cow = if url.starts_with("$") { + if url.starts_with("$flow:") || url.starts_with("$script:") { + let path = url.splitn(2, ':').nth(1).unwrap(); + Cow::Owned( + get_url_from_runnable( + path, + url.starts_with("$flow:"), + &db, + authed, + test_websocket.url_runnable_args.as_ref(), + &workspace_id, + ) + .await?, + ) + } else { + return Err(error::Error::BadConfig(format!( + "Invalid WebSocket runnable path: {}", + url + ))); + } + } else { + Cow::Borrowed(&url) + }; + + connect_async(connect_url.as_ref()).await.map_err(|err| { + error::Error::BadConfig(format!( + "Error connecting to WebSocket: {}", + err.to_string() + )) + })?; + + Ok(()) + }; + + tokio::time::timeout(tokio::time::Duration::from_secs(30), connect_f) + .await + .map_err(|_| { + error::Error::BadConfig(format!("Timeout connecting to WebSocket after 30 seconds")) + })??; + + Ok(()) +} + async fn listen_to_unlistened_websockets( db: &DB, killpill_rx: &tokio::sync::broadcast::Receiver<()>, @@ -390,13 +456,13 @@ async fn listen_to_unlistened_websockets( .await { Ok(mut triggers) => { - triggers.shuffle(&mut rand::thread_rng()); + triggers.shuffle(&mut rand::rng()); for trigger in triggers { trigger.maybe_listen_to_websocket(db.clone(), killpill_rx.resubscribe()).await; } } Err(err) => { - tracing::error!("Error fetching websocket triggers: {:?}", err); + tracing::error!("Error fetching WebSocket triggers: {:?}", err); } }; @@ -408,18 +474,18 @@ async fn listen_to_unlistened_websockets( .await { Ok(mut captures) => { - captures.shuffle(&mut rand::thread_rng()); + captures.shuffle(&mut rand::rng()); for capture in captures { capture.maybe_listen_to_websocket(db.clone(), killpill_rx.resubscribe()).await; } } Err(err) => { - tracing::error!("Error fetching capture websocket triggers: {:?}", err); + tracing::error!("Error fetching capture WebSocket triggers: {:?}", err); } } } -pub async fn start_websockets(db: DB, mut killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () { +pub fn start_websockets(db: DB, mut killpill_rx: tokio::sync::broadcast::Receiver<()>) -> () { tokio::spawn(async move { listen_to_unlistened_websockets(&db, &killpill_rx).await; loop { @@ -502,17 +568,9 @@ where deserializer.deserialize_map(SupersetVisitor { key, value_to_check }) } -async fn wait_runnable_result( - path: String, - is_flow: bool, +fn raw_value_to_args_hashmap( args: Option<&Box>, - authed: ApiAuthed, - db: &DB, - workspace_id: &str, - trigger_path: &str, -) -> error::Result { - let user_db = UserDB::new(db.clone()); - +) -> error::Result>> { let args = if let Some(args) = args { serde_json::from_str::>>>(args.get()) .map_err(|e| error::Error::BadRequest(format!("invalid json: {}", e)))? @@ -520,81 +578,89 @@ async fn wait_runnable_result( } else { HashMap::new() }; + Ok(args) +} - let label_prefix = Some(format!("ws-{}-", trigger_path)); - let (_, job_id) = if is_flow { - run_flow_by_path_inner( +async fn wait_runnable_result( + path: String, + is_flow: bool, + args: PushArgsOwned, + authed: ApiAuthed, + db: &DB, + workspace_id: &str, +) -> error::Result { + let user_db = UserDB::new(db.clone()); + + let username = authed.display_username().to_owned(); + + let (job_id, early_return) = if is_flow { + let (_, job_id) = run_flow_by_path_inner( authed, db.clone(), user_db, workspace_id.to_string(), StripPath(path.clone()), RunJobQuery::default(), - PushArgsOwned { args, extra: None }, - label_prefix, + args, + None, ) + .await?; + + let early_return = sqlx::query_scalar!( + r#"SELECT flow_version.value->>'early_return' as early_return + FROM flow + LEFT JOIN flow_version + ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] + WHERE flow.path = $1 and flow.workspace_id = $2"#, + path, + workspace_id, + ) + .fetch_optional(db) .await? + .flatten(); + + (job_id, early_return) } else { - run_script_by_path_inner( + let (_, job_id) = run_script_by_path_inner( authed, db.clone(), user_db, workspace_id.to_string(), StripPath(path.clone()), RunJobQuery::default(), - PushArgsOwned { args, extra: None }, - label_prefix, + args, + None, ) - .await? + .await?; + + (job_id, None) }; - let start_time = tokio::time::Instant::now(); - - loop { - if start_time.elapsed() > tokio::time::Duration::from_secs(300) { - return Err(anyhow::anyhow!( - "Timed out after 5m waiting for runnable {path} (is_flow: {is_flow}) to complete", - ) - .into()); - } - - #[derive(sqlx::FromRow)] - struct RawResult { - result: Option>>, - success: bool, - } - - let result = sqlx::query_as::<_, RawResult>( - "SELECT result, success FROM completed_job WHERE id = $1 AND workspace_id = $2", + let (result, success) = run_wait_result_internal( + db, + Uuid::parse_str(&job_id).unwrap(), + workspace_id.to_string(), + early_return, + &username, + ) + .await + .with_context(|| { + format!( + "Error fetching job result for {} {}", + if is_flow { "flow" } else { "script" }, + path ) - .bind(Uuid::parse_str(&job_id).unwrap()) - .bind(workspace_id) - .fetch_optional(db) - .await; + })?; - match result { - Ok(Some(r)) => { - if !r.success { - return Err(anyhow::anyhow!( - "Runnable {path} (is_flow: {is_flow}) failed: {:?}", - r.result - ) - .into()); - } else { - return Ok(r.result.map(|r| r.get().to_owned()).unwrap_or_default()); - } - } - Ok(None) => { - // not yet done, wait for 5s and check again - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - } - Err(err) => { - return Err(anyhow::anyhow!( - "Error fetching job result for runnable {path} (is_flow: {is_flow}): {err}", - ) - .into()); - } - } + if !success { + Err(anyhow::anyhow!( + "{} {path} failed: {:?}", + if is_flow { "Flow" } else { "Script" }, + result + ) + .into()) + } else { + Ok(result.get().to_owned()) } } @@ -614,26 +680,32 @@ async fn get_url_from_runnable( authed: ApiAuthed, args: Option<&Box>, workspace_id: &str, - trigger_path: &str, ) -> error::Result { - tracing::info!("Running runnable {path} (is_flow: {is_flow}) to get websocket URL",); + tracing::info!( + "Running {} {} to get WebSocket URL", + if is_flow { "flow" } else { "script" }, + path + ); + + let args = raw_value_to_args_hashmap(args)?; let result = wait_runnable_result( path.to_string(), is_flow, - args, + PushArgsOwned { args, extra: None }, authed, db, workspace_id, - trigger_path, ) .await?; - if result.starts_with("\"") && result.ends_with("\"") { - Ok(result[1..result.len() - 1].to_string()) - } else { - Err(anyhow::anyhow!("Runnable {path} (is_flow: {is_flow}) did not return a string").into()) - } + serde_json::from_str::(result.as_str()).map_err(|_| { + error::Error::BadConfig(format!( + "{} {} did not return a string", + if is_flow { "Flow" } else { "Script" }, + path, + )) + }) } impl WebsocketTrigger { @@ -652,11 +724,11 @@ impl WebsocketTrigger { if has_lock.flatten().unwrap_or(false) { tokio::spawn(listen_to_websocket(WebsocketEnum::Trigger(self), db, killpill_rx)); } else { - tracing::info!("Websocket {} already being listened to", self.url); + tracing::info!("WebSocket {} already being listened to", self.url); } }, Err(err) => { - tracing::error!("Error acquiring lock for websocket {}: {:?}", self.path, err); + tracing::error!("Error acquiring lock for WebSocket {}: {:?}", self.path, err); } }; } @@ -677,12 +749,12 @@ impl WebsocketTrigger { self.workspace_id, self.path, ).execute(db).await.ok(); - tracing::info!("Websocket {} changed, disabled, or deleted, stopping...", self.url); + tracing::info!("WebSocket {} changed, disabled, or deleted, stopping...", self.url); return None; } }, Err(err) => { - tracing::warn!("Error updating ping of websocket {}: {:?}", self.url, err); + tracing::warn!("Error updating ping of WebSocket {}: {:?}", self.url, err); } }; @@ -698,11 +770,11 @@ impl WebsocketTrigger { ) .execute(db).await { Ok(_) => { - report_critical_error(format!("Disabling websocket {} because of error: {}", self.url, error), db.clone(), Some(&self.workspace_id), None).await; + report_critical_error(format!("Disabling WebSocket {} because of error: {}", self.url, error), db.clone(), Some(&self.workspace_id), None).await; }, Err(disable_err) => { report_critical_error( - format!("Could not disable websocket {} with err {}, disabling because of error {}", self.path, disable_err, error), + format!("Could not disable WebSocket {} with err {}, disabling because of error {}", self.path, disable_err, error), db.clone(), Some(&self.workspace_id), None, @@ -721,17 +793,16 @@ impl WebsocketTrigger { &path, is_flow, db, - self.fetch_authed(db, Some("url".to_string())).await?, + self.fetch_authed(db).await?, self.url_runnable_args.as_ref().map(|r| &r.0), &self.workspace_id, - &self.path, ) .await } async fn send_initial_messages( &self, - mut writer: SplitSink>, Message>, + writer: &mut SplitSink>, Message>, db: &DB, ) -> error::Result<()> { let initial_messages: Vec = self @@ -751,7 +822,7 @@ impl WebsocketTrigger { msg }; tracing::info!( - "Sending raw message initial message to websocket {}: {}", + "Sending raw message initial message to WebSocket {}: {}", self.url, msg ); @@ -763,38 +834,45 @@ impl WebsocketTrigger { } InitialMessage::RunnableResult { path, is_flow, args } => { tracing::info!( - "Running runnable {path} (is_flow: {is_flow}) for initial message to websocket {}", + "Running {} {} for initial message to WebSocket {}", + if is_flow { "flow" } else { "script" }, + path, self.url, ); + let args = raw_value_to_args_hashmap(Some(&args))?; + let result = wait_runnable_result( path.clone(), is_flow, - Some(&args), - self.fetch_authed(db, Some("init".to_string())).await?, + PushArgsOwned { args, extra: None }, + self.fetch_authed(db).await?, db, &self.workspace_id, - &self.path, ) .await?; tracing::info!( - "Sending runnable {path} (is_flow: {is_flow}) result to websocket {}", + "Sending {} {} result to WebSocket {}", + if is_flow { "flow" } else { "script" }, + path, self.url ); - let result = if result.starts_with("\"") && result.ends_with("\"") { - result[1..result.len() - 1].to_string() - } else { - result - }; + // if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string. + // it falls back to the original serialized JSON if it doesn't work. + let result = serde_json::from_str::(result.as_str()).unwrap_or(result); writer .send(tokio_tungstenite::tungstenite::Message::Text(result)) .await .map_err(to_anyhow) .with_context(|| { - format!("Failed to send runnable {path} (is_flow: {is_flow}) result") + format!( + "Failed to send {} {} result", + if is_flow { "flow" } else { "script" }, + path + ) })?; } } @@ -803,11 +881,16 @@ impl WebsocketTrigger { Ok(()) } - async fn handle(&self, db: &DB, args: PushArgsOwned) -> () { - if let Err(err) = run_job(db, self, args).await { + async fn handle( + &self, + db: &DB, + args: PushArgsOwned, + return_message_channels: Option, + ) -> () { + if let Err(err) = run_job(db, self, args, return_message_channels).await { report_critical_error( format!( - "Failed to trigger job from websocket {}: {:?}", + "Failed to trigger job from WebSocket {}: {:?}", self.url, err ), db.clone(), @@ -818,17 +901,13 @@ impl WebsocketTrigger { }; } - async fn fetch_authed( - &self, - db: &DB, - username_override: Option, - ) -> error::Result { + async fn fetch_authed(&self, db: &DB) -> error::Result { fetch_api_authed( self.edited_by.clone(), self.email.clone(), &self.workspace_id, db, - username_override, + Some(format!("ws-{}", self.path)), ) .await } @@ -861,11 +940,11 @@ impl CaptureConfigForWebsocket { if has_lock.flatten().unwrap_or(false) { tokio::spawn(listen_to_websocket(WebsocketEnum::Capture(self), db, killpill_rx)); } else { - tracing::info!("Websocket {} already being listened to", self.trigger_config.url); + tracing::info!("WebSocket {} already being listened to", self.trigger_config.url); } }, Err(err) => { - tracing::error!("Error acquiring lock for capture websocket {}: {:?}", self.path, err); + tracing::error!("Error acquiring lock for capture WebSocket {}: {:?}", self.path, err); } }; } @@ -888,12 +967,12 @@ impl CaptureConfigForWebsocket { self.path, self.is_flow, ).execute(db).await.ok(); - tracing::info!("Websocket capture {} changed, disabled, or deleted, stopping...", self.trigger_config.url); + tracing::info!("WebSocket capture {} changed, disabled, or deleted, stopping...", self.trigger_config.url); return None; } }, Err(err) => { - tracing::warn!("Error updating ping of capture websocket {}: {:?}", self.trigger_config.url, err); + tracing::warn!("Error updating ping of capture WebSocket {}: {:?}", self.trigger_config.url, err); } }; @@ -932,25 +1011,20 @@ impl CaptureConfigForWebsocket { &path, is_flow, db, - self.fetch_authed(db, Some("url".to_string())).await?, + self.fetch_authed(db).await?, url_runnable_args.as_ref(), &self.workspace_id, - &self.get_trigger_path(), ) .await } - async fn fetch_authed( - &self, - db: &DB, - username_override: Option, - ) -> error::Result { + async fn fetch_authed(&self, db: &DB) -> error::Result { fetch_api_authed( self.owner.clone(), self.email.clone(), &self.workspace_id, db, - username_override, + Some(format!("ws-{}", self.get_trigger_path())), ) .await } @@ -964,7 +1038,7 @@ impl CaptureConfigForWebsocket { self.is_flow, ) .execute(db).await { - tracing::error!("Could not disable websocket capture {} ({}) with err {}, disabling because of error {}", self.path, self.workspace_id, err, error); + tracing::error!("Could not disable WebSocket capture {} ({}) with err {}, disabling because of error {}", self.path, self.workspace_id, err, error); } } @@ -1012,18 +1086,28 @@ impl WebsocketEnum { } } +struct ReturnMessageChannels { + send_message_tx: tokio::sync::mpsc::Sender, + killpill_rx: tokio::sync::broadcast::Receiver<()>, +} + +impl Clone for ReturnMessageChannels { + fn clone(&self) -> Self { + Self { + send_message_tx: self.send_message_tx.clone(), + killpill_rx: self.killpill_rx.resubscribe(), + } + } +} + async fn listen_to_websocket( ws: WebsocketEnum, db: DB, mut killpill_rx: tokio::sync::broadcast::Receiver<()>, ) -> () { - if let None = ws.update_ping(&db, Some("Connecting")).await { - return; - } - let url = match &ws { - WebsocketEnum::Trigger(ws_trigger) => &ws_trigger.url, - WebsocketEnum::Capture(capture) => &capture.trigger_config.url, + WebsocketEnum::Trigger(ws_trigger) => ws_trigger.url.clone(), + WebsocketEnum::Capture(capture) => capture.trigger_config.url.clone(), }; let filters: Vec = match &ws { @@ -1035,226 +1119,271 @@ async fn listen_to_websocket( WebsocketEnum::Capture(_) => vec![], }; - loop { - let connect_url: Cow = if url.starts_with("$") { - if url.starts_with("$flow:") || url.starts_with("$script:") { - let path = url.splitn(2, ':').nth(1).unwrap(); - tokio::select! { - biased; - _ = killpill_rx.recv() => { - return; - }, - _ = loop_ping(&db, &ws, Some( - "Waiting on runnable to return websocket URL..." - )) => { - return; - }, + let connect_url: Cow = if url.starts_with("$") { + if url.starts_with("$flow:") || url.starts_with("$script:") { + let path = url.splitn(2, ':').nth(1).unwrap(); + tokio::select! { + biased; + _ = killpill_rx.recv() => { + return; + }, + _ = loop_ping(&db, &ws, Some( + "Waiting on runnable to return WebSocket URL..." + )) => { + return; + }, - - url_result = ws.get_url_from_runnable(path, url.starts_with("$flow:"), &db) => match url_result { - Ok(url) => Cow::Owned(url), - Err(err) => { - ws.disable_with_error(&db, format!( - "Error getting websocket URL from runnable after 5 tries: {:?}", - err - ), - ) - .await; - return; - } - }, - } - } else { - ws.disable_with_error(&db, format!("Invalid websocket runnable path: {}", url)) - .await; - return; + url_result = ws.get_url_from_runnable(path, url.starts_with("$flow:"), &db) => match url_result { + Ok(url) => Cow::Owned(url), + Err(err) => { + ws.disable_with_error(&db, format!( + "Error getting WebSocket URL from runnable after 5 tries: {:?}", + err + ), + ) + .await; + return; + } + }, } } else { - Cow::Borrowed(url) - }; + ws.disable_with_error(&db, format!("Invalid WebSocket runnable path: {}", url)) + .await; + return; + } + } else { + Cow::Borrowed(&url) + }; - tokio::select! { - biased; - _ = killpill_rx.recv() => { - return; - }, - _ = loop_ping(&db, &ws, Some("Connecting...")) => { - return; - }, - connection = connect_async(connect_url.as_ref()) => { - match connection { - Ok((ws_stream, _)) => { - tracing::info!("Listening to websocket {}", url); - if let None = ws.update_ping(&db, None).await { - return; - } - let (writer, mut reader) = ws_stream.split(); - let mut last_ping = tokio::time::Instant::now(); + tokio::select! { + biased; + _ = killpill_rx.recv() => { + return; + }, + _ = loop_ping(&db, &ws, Some("Connecting...")) => { + return; + }, + connection = connect_async(connect_url.as_ref()) => { + match connection { + Ok((ws_stream, _)) => { + tracing::info!("Connected to WebSocket {}", url); + let (mut writer, mut reader) = ws_stream.split(); - - // send initial messages - match &ws { - WebsocketEnum::Trigger(ws_trigger) => { - tokio::select! { - biased; - _ = killpill_rx.recv() => { - return; - }, - _ = loop_ping(&db, &ws, Some("Sending initial messages...")) => { - return; - }, - result = ws_trigger.send_initial_messages(writer, &db) => { - if let Err(err) = result { - ws_trigger.disable_with_error(&db, format!("Error sending initial messages: {:?}", err)).await; - return - } else { - tracing::debug!("Initial messages sent successfully to websocket {}", url); - } - } - } - }, - _ => {} - } - - loop { + // send initial messages + match &ws { + WebsocketEnum::Trigger(ws_trigger) => { tokio::select! { biased; _ = killpill_rx.recv() => { return; - } - msg = reader.next() => { - if let Some(msg) = msg { - if last_ping.elapsed() > tokio::time::Duration::from_secs(5) { - if let None = ws.update_ping(&db, None).await { - return; - } - last_ping = tokio::time::Instant::now(); - } - match msg { - Ok(msg) => { - match msg { - tokio_tungstenite::tungstenite::Message::Text(text) => { - tracing::debug!("Received text message from websocket {}: {}", url, text); - let mut should_handle = true; - for filter in &filters { - match filter { - Filter::JsonFilter(JsonFilter { key, value }) => { - let mut deserializer = serde_json::Deserializer::from_str(text.as_str()); - should_handle = match is_value_superset(&mut deserializer, key, &value) { - Ok(filter_match) => { - filter_match - }, - Err(err) => { - tracing::warn!("Error deserializing filter for websocket {}: {:?}", url, err); - false - } - }; - } - } - if !should_handle { - break; - } - } - if should_handle { - - let args = HashMap::from([("msg".to_string(), to_raw_value(&text))]); - let extra = Some(HashMap::from([( - "wm_trigger".to_string(), - to_raw_value(&serde_json::json!({"kind": "websocket", "websocket": { "url": url }})), - )])); - - let args = PushArgsOwned { args, extra }; - match &ws { - WebsocketEnum::Trigger(ws_trigger) => { - ws_trigger.handle(&db, args).await; - }, - WebsocketEnum::Capture(capture) => { - capture.handle(&db, args).await; - } - } - } - }, - a @ _ => { - tracing::debug!("Received non text-message from websocket {}: {:?}", url, a); - } - } - }, - Err(err) => { - tracing::error!("Error reading from websocket {}: {:?}", url, err); - } - } + }, + _ = loop_ping(&db, &ws, Some("Sending initial messages...")) => { + return; + }, + result = ws_trigger.send_initial_messages(&mut writer, &db) => { + if let Err(err) = result { + ws_trigger.disable_with_error(&db, format!("Error sending initial messages: {:?}", err)).await; + return } else { - tracing::error!("Websocket {} closed", url); - if let None = ws.update_ping(&db, Some("Websocket closed")).await { - return; - } - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - break; + tracing::debug!("Initial messages sent successfully to WebSocket {}", url); } - }, - _ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => { - tracing::debug!("Sending ping to websocket {}", url); - if let None = ws.update_ping(&db, None).await { - return; - } - last_ping = tokio::time::Instant::now(); - }, + } } + }, + _ => { } } - Err(err) => { - tracing::error!("Error connecting to websocket {}: {:?}", url, err); - if let None = ws.update_ping(&db, Some(err.to_string().as_str())).await { - return; - } - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + let (return_message_channels, message_sender_handle) = match &ws { + WebsocketEnum::Trigger(ws_trigger) if ws_trigger.can_return_message => { + let (send_message_tx, mut rx) = tokio::sync::mpsc::channel::(100); + let w_id = ws_trigger.workspace_id.clone(); + let url = ws_trigger.url.clone(); + let db = db.clone(); + let handle = tokio::spawn(async move { + while let Some(message) = rx.recv().await { + if let Err(err) = writer.send(tokio_tungstenite::tungstenite::Message::Text(message)).await { + report_critical_error(format!("Could not send runnable result to WebSocket {} because of error: {}", url, err), db.clone(), Some(&w_id), None).await; + } + } + }); + + let killpill_rx = killpill_rx.resubscribe(); + + let return_message_channels = ReturnMessageChannels { + send_message_tx, + killpill_rx + }; + + (Some(return_message_channels), Some(handle)) + }, + _ => (None, None) + }; + + tokio::select! { + biased; + _ = killpill_rx.recv() => {}, + _ = loop_ping(&db, &ws, None) => {}, + _ = async { + loop { + if let Some(msg) = reader.next().await { + match msg { + Ok(msg) => { + match msg { + tokio_tungstenite::tungstenite::Message::Text(text) => { + tracing::debug!("Received text message from WebSocket {}: {}", url, text); + let mut should_handle = true; + for filter in &filters { + match filter { + Filter::JsonFilter(JsonFilter { key, value }) => { + let mut deserializer = serde_json::Deserializer::from_str(text.as_str()); + should_handle = match is_value_superset(&mut deserializer, key, &value) { + Ok(filter_match) => { + filter_match + }, + Err(err) => { + tracing::warn!("Error deserializing filter for WebSocket {}: {:?}", url, err); + false + } + }; + } + } + if !should_handle { + break; + } + } + if should_handle { + + let args = HashMap::from([("msg".to_string(), to_raw_value(&text))]); + let extra = Some(HashMap::from([( + "wm_trigger".to_string(), + to_raw_value(&serde_json::json!({"kind": "websocket", "websocket": { "url": url }})), + )])); + + let args = PushArgsOwned { args, extra }; + match &ws { + WebsocketEnum::Trigger(ws_trigger) => { + ws_trigger.handle(&db, args, return_message_channels.clone()).await; + }, + WebsocketEnum::Capture(capture) => { + capture.handle(&db, args).await; + }, + } + } + }, + a @ _ => { + tracing::debug!("Received non text-message from WebSocket {}: {:?}", url, a); + } + } + }, + Err(err) => { + tracing::error!("Error reading from WebSocket {}: {:?}", url, err); + } + } + } else { + tracing::error!("WebSocket {} closed", url); + ws.update_ping(&db, Some("WebSocket closed")).await; + break; + } + } + } => {} } + // make sure to stop return message handler + if let Some(message_sender_handle) = message_sender_handle { + message_sender_handle.abort(); + } + } + Err(err) => { + tracing::error!("Error connecting to WebSocket {}: {:?}", url, err); + ws.update_ping(&db, Some(err.to_string().as_str())).await; } } } } } -async fn run_job(db: &DB, trigger: &WebsocketTrigger, args: PushArgsOwned) -> anyhow::Result<()> { - let label_prefix = Some(format!("ws-{}-", trigger.path)); - +async fn run_job( + db: &DB, + trigger: &WebsocketTrigger, + args: PushArgsOwned, + return_message_channels: Option, +) -> anyhow::Result<()> { let authed = fetch_api_authed( trigger.edited_by.clone(), trigger.email.clone(), &trigger.workspace_id, db, - Some("anonymous".to_string()), + Some(format!("ws-{}", trigger.path)), ) .await?; - let user_db = UserDB::new(db.clone()); + if let Some(ReturnMessageChannels { send_message_tx, mut killpill_rx }) = + return_message_channels + { + let db_ = db.clone(); + let url = trigger.url.clone(); + let script_path = trigger.script_path.clone(); + let is_flow = trigger.is_flow; + let w_id = trigger.workspace_id.clone(); + let handle_response_f = async move { + tokio::select! { + _ = killpill_rx.recv() => { + return; + }, + result = wait_runnable_result( + script_path, + is_flow, + args, + authed, + &db_, + &w_id, + ) => { + if let Ok(result) = result { + // only send the result if it's not null + if result != "null" { + tracing::info!("Sending job result to WebSocket {}", url); + // if the `result` was just a single string, the below removes the surrounding quotes by parsing it as a string. + // it falls back to the original serialized JSON if it doesn't work. + let result = serde_json::from_str::(result.as_str()).unwrap_or(result); + if let Err(err) = send_message_tx.send(result).await { + report_critical_error(format!("Could not send runnable result to WebSocket {} because of error: {}", url, err), db_.clone(), Some(&w_id), None).await; + } + } + } + } + }; + }; - let run_query = RunJobQuery::default(); - - if trigger.is_flow { - run_flow_by_path_inner( - authed, - db.clone(), - user_db, - trigger.workspace_id.clone(), - StripPath(trigger.script_path.to_owned()), - run_query, - args, - label_prefix, - ) - .await?; + tokio::spawn(handle_response_f); } else { - run_script_by_path_inner( - authed, - db.clone(), - user_db, - trigger.workspace_id.clone(), - StripPath(trigger.script_path.to_owned()), - run_query, - args, - label_prefix, - ) - .await?; + let user_db = UserDB::new(db.clone()); + let run_query = RunJobQuery::default(); + let runnable_path = StripPath(trigger.script_path.to_owned()); + if trigger.is_flow { + run_flow_by_path_inner( + authed, + db.clone(), + user_db, + trigger.workspace_id.clone(), + runnable_path, + run_query, + args, + None, + ) + .await?; + } else { + run_script_by_path_inner( + authed, + db.clone(), + user_db, + trigger.workspace_id.clone(), + runnable_path, + run_query, + args, + None, + ) + .await?; + } } Ok(()) diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index a4adc61977..3758f5def4 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -121,7 +121,8 @@ pub fn workspaced_service() -> Router { "/critical_alerts/acknowledge_all", post(acknowledge_all_critical_alerts), ) - .route("/critical_alerts/mute", post(mute_critical_alerts)); + .route("/critical_alerts/mute", post(mute_critical_alerts)) + .route("/operator_settings", post(update_operator_settings)); #[cfg(feature = "stripe")] { @@ -188,6 +189,7 @@ pub struct WorkspaceSettings { pub default_scripts: Option, pub mute_critical_alerts: Option, pub color: Option, + pub operator_settings: Option, } #[derive(FromRow, Serialize, Debug)] @@ -286,6 +288,7 @@ struct UserWorkspace { pub name: String, pub username: String, pub color: Option, + pub operator_settings: Option>, } #[derive(Deserialize)] @@ -1366,7 +1369,8 @@ async fn user_workspaces( let mut tx = db.begin().await?; let workspaces = sqlx::query_as!( UserWorkspace, - "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color + "SELECT workspace.id, workspace.name, usr.username, workspace_settings.color, + CASE WHEN usr.operator THEN workspace_settings.operator_settings ELSE NULL END as operator_settings FROM workspace JOIN usr ON usr.workspace_id = workspace.id JOIN workspace_settings ON workspace_settings.workspace_id = workspace.id @@ -2135,3 +2139,41 @@ async fn mute_critical_alerts( pub async fn mute_critical_alerts() -> Error { Error::NotFound("Critical Alerts require EE".to_string()) } + +#[derive(Deserialize, Serialize)] +struct ChangeOperatorSettings { + runs: bool, + schedules: bool, + resources: bool, + variables: bool, + triggers: bool, + audit_logs: bool, + groups: bool, + folders: bool, + workers: bool, +} + +async fn update_operator_settings( + authed: ApiAuthed, + Path(w_id): Path, + Extension(db): Extension, + Json(settings): Json, +) -> Result { + require_admin(authed.is_admin, &authed.username)?; + + let mut tx = db.begin().await?; + + let settings_json = serde_json::json!(settings); + + sqlx::query!( + "UPDATE workspace_settings SET operator_settings = $1 WHERE workspace_id = $2", + settings_json, + &w_id + ) + .execute(&mut *tx) + .await?; + + tx.commit().await?; + + Ok("Operator settings updated successfully".to_string()) +} diff --git a/backend/windmill-common/src/cache.rs b/backend/windmill-common/src/cache.rs index 13d71dd402..83cec0b417 100644 --- a/backend/windmill-common/src/cache.rs +++ b/backend/windmill-common/src/cache.rs @@ -529,7 +529,7 @@ pub mod script { lock AS \"lock: String\", \ language AS \"language: Option\", \ envs AS \"envs: Vec\", \ - codebase AS \"codebase: String\" \ + codebase LIKE '%.tar' as use_tar \ FROM script WHERE hash = $1 LIMIT 1", hash.0 ) @@ -543,7 +543,16 @@ pub mod script { meta: Some(ScriptMetadata { language: r.language, envs: r.envs, - codebase: r.codebase, + codebase: if let Some(use_tar) = r.use_tar { + let sh = hash.to_string(); + if use_tar { + Some(format!("{sh}.tar")) + } else { + Some(sh) + } + } else { + None + }, }), }) }); diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 22458af9ba..b4e23bb342 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -161,8 +161,8 @@ impl Retry { if let Some(random_factor) = exponential.random_factor { if random_factor > 0 { let random_component = - rand::thread_rng().gen_range(0..(std::cmp::min(random_factor, 100) as u16)); - secs = match rand::thread_rng().gen_bool(1.0 / 2.0) { + rand::rng().random_range(0..(std::cmp::min(random_factor, 100) as u16)); + secs = match rand::rng().random_bool(1.0 / 2.0) { true => secs.saturating_add(secs * random_component / 100), false => secs.saturating_sub(secs * random_component / 100), }; diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index c13f96e2de..06a5e20863 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -19,7 +19,8 @@ use git_version::git_version; use chrono::Utc; use croner::Cron; -use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use rand::distr::Alphanumeric; +use rand::{rng, Rng}; use reqwest::Client; use semver::Version; use serde::{Deserialize, Serialize}; @@ -205,7 +206,7 @@ pub async fn http_get_from_hub( } pub fn rd_string(len: usize) -> String { - thread_rng() + rng() .sample_iter(&Alphanumeric) .take(len) .map(char::from) diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index 7f91758c90..353b26ecb5 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -6,12 +6,11 @@ * LICENSE-AGPL for a copy of the license. */ -use crate::error::Result; +use crate::error; use crate::{worker::WORKER_GROUP, BASE_URL, DB}; use chrono::{SecondsFormat, Utc}; use magic_crypt::{MagicCrypt256, MagicCryptError, MagicCryptTrait}; use serde::{Deserialize, Serialize}; -use crate::error; lazy_static::lazy_static! { pub static ref SECRET_SALT: Option = std::env::var("SECRET_SALT").ok(); @@ -134,7 +133,7 @@ pub async fn get_secret_value_as_admin( let value = variable.value; if !value.is_empty() { let mc = build_crypt(db, w_id).await?; - decrypt_value_with_mc(value, mc).await? + decrypt(&mc, value)? } else { "".to_string() } @@ -145,16 +144,6 @@ pub async fn get_secret_value_as_admin( Ok(r) } -pub async fn decrypt_value_with_mc(value: String, mc: MagicCrypt256) -> Result { - mc.decrypt_base64_to_string(value).map_err(|e| match e { - MagicCryptError::DecryptError(_) => crate::error::Error::InternalErr( - "Could not decrypt value. The value may have been encrypted with a different key." - .to_string(), - ), - _ => crate::error::Error::InternalErr(e.to_string()), - }) -} - pub fn encrypt(mc: &MagicCrypt256, value: &str) -> String { mc.encrypt_str_to_base64(value) } diff --git a/backend/windmill-worker/nsjail/run.python3.config.proto b/backend/windmill-worker/nsjail/run.python3.config.proto index a9d61dc8b1..b49b9cfbfe 100644 --- a/backend/windmill-worker/nsjail/run.python3.config.proto +++ b/backend/windmill-worker/nsjail/run.python3.config.proto @@ -123,6 +123,13 @@ mount { is_bind: true } +mount { + src: "{GLOBAL_SITE_PACKAGES}" + dst: "{GLOBAL_SITE_PACKAGES}" + is_bind: true + mandatory: false +} + {SHARED_MOUNT} {SHARED_DEPENDENCIES} diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index 61ec9cee6a..099300f621 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -18,7 +18,7 @@ use windmill_common::jobs::ENTRYPOINT_OVERRIDE; use windmill_common::s3_helpers::{ get_etag_or_empty, LargeFileStorage, ObjectStoreResource, S3Object, }; -use windmill_common::variables::{build_crypt_with_key_suffix, decrypt_value_with_mc}; +use windmill_common::variables::{build_crypt_with_key_suffix, decrypt}; use windmill_common::worker::{ to_raw_value, write_file, CLOUD_HOSTED, ROOT_CACHE_DIR, WORKER_CONFIG, }; @@ -63,10 +63,10 @@ pub fn check_executor_binary_exists( ) -> Result<(), Error> { if !Path::new(executor_path).exists() { #[cfg(feature = "enterprise")] - let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run {language} jobs.", executor_path); + let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-ee-full image. Please use the image `ghcr.io/windmill-labs/windmill-ee-full` for your instance in order to run {language} jobs.", executor_path); #[cfg(not(feature = "enterprise"))] - let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run {language} jobs.", executor_path); + let msg = format!("Couldn't find {executor} at {}. This probably means that you are not using the windmill-full image. Please use the image `ghcr.io/windmill-labs/windmill-full` for your instance in order to run {language} jobs.", executor_path); return Err(Error::NotFound(msg)); } @@ -255,11 +255,9 @@ pub async fn transform_json_value( let encrypted = y.strip_prefix("$encrypted:").unwrap(); let mc = build_crypt_with_key_suffix(&db, &job.workspace_id, &job.id.to_string()).await?; - decrypt_value_with_mc(encrypted.to_string(), mc) - .await - .and_then(|x| { - serde_json::from_str(&x).map_err(|e| Error::InternalErr(e.to_string())) - }) + decrypt(&mc, encrypted.to_string()).and_then(|x| { + serde_json::from_str(&x).map_err(|e| Error::InternalErr(e.to_string())) + }) // let path = y.strip_prefix("$res:").unwrap(); } diff --git a/backend/windmill-worker/src/mssql_executor.rs b/backend/windmill-worker/src/mssql_executor.rs index b97560e225..be5ea1e905 100644 --- a/backend/windmill-worker/src/mssql_executor.rs +++ b/backend/windmill-worker/src/mssql_executor.rs @@ -72,7 +72,10 @@ pub async fn do_mssql( let mut config = Config::new(); - config.host(database.host); + let host_ref = &database.host; + let port_ref = database.port; + + config.host(host_ref.clone()); config.database(database.dbname); let use_instance_name = database.instance_name.as_ref().is_some_and(|x| x != ""); if use_instance_name { @@ -101,12 +104,26 @@ pub async fn do_mssql( }; tcp.set_nodelay(true)?; - // To be able to use Tokio's tcp, we're using the `compat_write` from - // the `TokioAsyncWriteCompatExt` to get a stream compatible with the - // traits from the `futures` crate. - let mut client = Client::connect(config, tcp.compat_write()) - .await - .map_err(to_anyhow)?; + // NOTE Azure default behavior with SQL Server is to redirect: + // https://learn.microsoft.com/en-us/azure/azure-sql/database/connectivity-architecture?view=azuresql#connection-policy + // https://github.com/prisma/tiberius?tab=readme-ov-file#redirects + let mut client = match Client::connect(config.clone(), tcp.compat_write()).await { + Ok(client) => { + tracing::debug!("Connected to host: {:#?}, port: {:#?}", host_ref, port_ref); + client + } + Err(tiberius::error::Error::Routing { host, port }) => { + tracing::debug!("Redirecting to host: {:#?}, port: {:#?}", host, port); + config.host(&host); + config.port(port); + + let tcp = TcpStream::connect(config.get_addr()).await?; + tcp.set_nodelay(true)?; + + Client::connect(config, tcp.compat_write()).await.map_err(to_anyhow)? + } + Err(e) => return Err(Error::Anyhow(to_anyhow(e))), + }; let sig = parse_mssql_sig(&query) .map_err(|x| Error::ExecutionErr(x.to_string()))? diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index 22a488f62d..13b538d34c 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -36,36 +36,38 @@ use windmill_common::{ use windmill_common::variables::get_secret_value_as_admin; use windmill_queue::{append_logs, CanceledBy}; +use std::env::var; lazy_static::lazy_static! { static ref PYTHON_PATH: String = - std::env::var("PYTHON_PATH").unwrap_or_else(|_| "/usr/local/bin/python3".to_string()); + var("PYTHON_PATH").unwrap_or_else(|_| "/usr/local/bin/python3".to_string()); static ref UV_PATH: String = - std::env::var("UV_PATH").unwrap_or_else(|_| "/usr/local/bin/uv".to_string()); + var("UV_PATH").unwrap_or_else(|_| "/usr/local/bin/uv".to_string()); static ref PY_CONCURRENT_DOWNLOADS: usize = - std::env::var("PY_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20); + var("PY_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20); static ref FLOCK_PATH: String = - std::env::var("FLOCK_PATH").unwrap_or_else(|_| "/usr/bin/flock".to_string()); + var("FLOCK_PATH").unwrap_or_else(|_| "/usr/bin/flock".to_string()); static ref NON_ALPHANUM_CHAR: Regex = regex::Regex::new(r"[^0-9A-Za-z=.-]").unwrap(); - static ref PIP_TRUSTED_HOST: Option = std::env::var("PIP_TRUSTED_HOST").ok(); - static ref PIP_INDEX_CERT: Option = std::env::var("PIP_INDEX_CERT").ok(); + static ref TRUSTED_HOST: Option = var("PY_TRUSTED_HOST").ok().or(var("PIP_TRUSTED_HOST").ok()); + static ref INDEX_CERT: Option = var("PY_INDEX_CERT").ok().or(var("PIP_INDEX_CERT").ok()); + static ref NATIVE_CERT: bool = var("PY_NATIVE_CERT").ok().or(var("UV_NATIVE_TLS").ok()).map(|flag| flag == "true").unwrap_or(false); - pub static ref USE_SYSTEM_PYTHON: bool = std::env::var("USE_SYSTEM_PYTHON") + pub static ref USE_SYSTEM_PYTHON: bool = var("USE_SYSTEM_PYTHON") .ok().map(|flag| flag == "true").unwrap_or(false); - pub static ref USE_PIP_COMPILE: bool = std::env::var("USE_PIP_COMPILE") + pub static ref USE_PIP_COMPILE: bool = var("USE_PIP_COMPILE") .ok().map(|flag| flag == "true").unwrap_or(false); - pub static ref USE_PIP_INSTALL: bool = std::env::var("USE_PIP_INSTALL") + pub static ref USE_PIP_INSTALL: bool = var("USE_PIP_INSTALL") .ok().map(|flag| flag == "true").unwrap_or(false); static ref RELATIVE_IMPORT_REGEX: Regex = Regex::new(r#"(import|from)\s(((u|f)\.)|\.)"#).unwrap(); - static ref EPHEMERAL_TOKEN_CMD: Option = std::env::var("EPHEMERAL_TOKEN_CMD").ok(); + static ref EPHEMERAL_TOKEN_CMD: Option = var("EPHEMERAL_TOKEN_CMD").ok(); } const NSJAIL_CONFIG_DOWNLOAD_PY_CONTENT: &str = include_str!("../nsjail/download.py.config.proto"); @@ -533,10 +535,10 @@ pub async fn uv_pip_compile( args.extend(["--index-url", url, "--no-emit-index-url"]); pip_args.push(format!("--index-url {}", url)); } - if let Some(host) = PIP_TRUSTED_HOST.as_ref() { + if let Some(host) = TRUSTED_HOST.as_ref() { args.extend(["--trusted-host", host]); } - if let Some(cert_path) = PIP_INDEX_CERT.as_ref() { + if let Some(cert_path) = INDEX_CERT.as_ref() { args.extend(["--cert", cert_path]); } let pip_args_str = pip_args.join(" "); @@ -622,12 +624,15 @@ pub async fn uv_pip_compile( if let Some(url) = pip_index_url.as_ref() { args.extend(["--index-url", url]); } - if let Some(host) = PIP_TRUSTED_HOST.as_ref() { + if let Some(host) = TRUSTED_HOST.as_ref() { args.extend(["--trusted-host", host]); } - if let Some(cert_path) = PIP_INDEX_CERT.as_ref() { + if let Some(cert_path) = INDEX_CERT.as_ref() { args.extend(["--cert", cert_path]); } + if *NATIVE_CERT { + args.extend(["--native-tls"]); + } tracing::error!("uv args: {:?}", args); #[cfg(windows)] @@ -898,6 +903,8 @@ pub async fn handle_python_job( tracing::debug!("Finished deps postinstall stage"); } + + if no_uv { append_logs( &job.id, @@ -1046,7 +1053,27 @@ except BaseException as e: let client = client.get_authed().await; let mut reserved_variables = get_reserved_variables(job, &client.token, db).await?; - let additional_python_paths_folders = additional_python_paths.iter().join(":"); + + // Add /tmp/windmill/cache/python_xyz/global-site-packages to PYTHONPATH. + // Usefull if certain wheels needs to be preinstalled before execution. + let global_site_packages_path = py_version.to_cache_dir() + "/global-site-packages"; + let additional_python_paths_folders = { + let mut paths= additional_python_paths.clone(); + if std::fs::metadata(&global_site_packages_path).is_ok() { + // We want global_site_packages_path to be included in additonal_python_paths_folders, but + // we don't want it to be included in global_site_packages_path. + // The reason for this is that additional_python_paths_folders is used to fill PYTHONPATH env variable for jailed script + // When global_site_packages_path used to place mount point of wheels to the jail config. + // Since we handle mount of global_site_packages on our own, we don't want it to be mounted automatically. + // We do this because existence of every wheel in cache is mandatory and if it is not there and nsjail expects it, it is a bug. + // On the other side global_site_packages is purely optional. + // NOTE: This behaviour can be changed in future, so verification of wheels can be offloaded from nsjail to windmill + paths.insert(0, global_site_packages_path.clone()); + // ^^^^^^^^ + // We also want this be priorotized, that's why we insert it to the beginning + } + paths.iter().join(":") + }; #[cfg(windows)] let additional_python_paths_folders = additional_python_paths_folders.replace(":", ";"); @@ -1076,6 +1103,7 @@ mount {{ .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) .replace("{SHARED_MOUNT}", shared_mount) .replace("{SHARED_DEPENDENCIES}", shared_deps.as_str()) + .replace("{GLOBAL_SITE_PACKAGES}", &global_site_packages_path) .replace("{MAIN}", format!("{dirs}/{last}").as_str()) .replace( "{ADDITIONAL_PYTHON_PATHS}", @@ -1573,12 +1601,15 @@ async fn spawn_uv_install( if let Some(url) = pip_index_url.as_ref() { vars.push(("INDEX_URL", url)); } - if let Some(cert_path) = PIP_INDEX_CERT.as_ref() { - vars.push(("PIP_INDEX_CERT", cert_path)); + if let Some(cert_path) = INDEX_CERT.as_ref() { + vars.push(("SSL_CERT_FILE", cert_path)); } - if let Some(host) = PIP_TRUSTED_HOST.as_ref() { + if let Some(host) = TRUSTED_HOST.as_ref() { vars.push(("TRUSTED_HOST", host)); } + if *NATIVE_CERT { + vars.push(("UV_NATIVE_TLS", "true")); + } let _owner; if let Some(py_path) = py_path.as_ref() { _owner = format!( @@ -1675,18 +1706,26 @@ async fn spawn_uv_install( }); } + let mut envs = vec![("PATH", PATH_ENV.as_str())]; + envs.push(("HOME", HOME_ENV.as_str())); + if let Some(url) = pip_index_url.as_ref() { command_args.extend(["--index-url", url]); } - if let Some(cert_path) = PIP_INDEX_CERT.as_ref() { - command_args.extend(["--cert", cert_path]); - } - if let Some(host) = PIP_TRUSTED_HOST.as_ref() { + if let Some(host) = TRUSTED_HOST.as_ref() { command_args.extend(["--trusted-host", &host]); } - - let mut envs = vec![("PATH", PATH_ENV.as_str())]; - envs.push(("HOME", HOME_ENV.as_str())); + if *NATIVE_CERT { + command_args.extend(["--native-tls"]); + } + // TODO: + // Track https://github.com/astral-sh/uv/issues/6715 + if let Some(cert_path) = INDEX_CERT.as_ref() { + // Once merged --cert can be used instead + // + // command_args.extend(["--cert", cert_path]); + envs.push(("SSL_CERT_FILE", cert_path)); + } tracing::debug!("uv pip install command: {:?}", command_args); @@ -2159,6 +2198,18 @@ pub async fn handle_python_reqs( db ).await; pids.lock().await.get_mut(i).and_then(|e| e.take()); + + // Create a file to indicate that installation was successfull + let valid_path = venv_p.clone() + "/.valid.windmill"; + // This is atomic operation, meaning, that it either completes and wheel is valid, + // or it does not and wheel is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await{ + tracing::error!( + workspace_id = %w_id, + job_id = %job_id, + "Failed to create {}!\n{e}\n + This file needed for python jobs to function", valid_path) + }; return Ok(()); } } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 0d657fbf16..68f044c837 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -1095,7 +1095,7 @@ pub async fn run_worker( #[cfg(feature = "benchmark")] let mut infos = BenchmarkInfo::new(); - let vacuum_shift = rand::thread_rng().gen_range(0..VACUUM_PERIOD); + let vacuum_shift = rand::rng().random_range(0..VACUUM_PERIOD); IS_READY.store(true, Ordering::Relaxed); tracing::info!( diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index b320b9a445..cf24258b9e 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -62,6 +62,8 @@ use windmill_queue::{ type DB = sqlx::Pool; +use windmill_audit::audit_ee::{audit_log, AuditAuthor}; +use windmill_audit::ActionKind; use windmill_queue::{canceled_job_to_result, push}; // #[instrument(level = "trace", skip_all)] @@ -1894,6 +1896,12 @@ async fn push_next_flow_job( )) && suspend.continue_on_disapprove_timeout.unwrap_or(false); + let audit_author = AuditAuthor { + username: flow_job.permissioned_as.trim_start_matches("u/").to_string(), + email: flow_job.email.clone(), + username_override: None, + }; + if can_be_resumed || disapproved_or_timeout_but_continue { if disapproved_or_timeout_but_continue { let js = if let Some(disapproved) = is_disapproved.as_ref() { @@ -1903,7 +1911,18 @@ async fn push_next_flow_job( }; resume_messages.push(to_raw_value(&js)); + audit_log( + &mut *tx, + &audit_author, + "jobs.suspend_resume", + ActionKind::Update, + &flow_job.workspace_id, + Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval but can continue".to_string()}).to_string()), + None, + ) + .await?; } + sqlx::query( "UPDATE queue SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'approvers'], $2) @@ -1971,6 +1990,18 @@ async fn push_next_flow_job( /* cancelled or we're WaitingForEvents but we don't have enough messages (timed out) */ } else { + if is_disapproved.is_none() { + audit_log( + &mut *tx, + &audit_author, + "jobs.suspend_resume", + ActionKind::Update, + &flow_job.workspace_id, + Some(&serde_json::json!({"approved": false, "job_id": flow_job.id, "details": "Suspend timed out without approval and is cancelled".to_string()}).to_string()), + None, + ) + .await?; + } tx.commit().await?; let (logs, error_name) = if let Some(disapprover) = is_disapproved { diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index cedf27fb7b..34645c4d33 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.448.1"; +export const VERSION = "v1.454.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/main.ts b/cli/main.ts index 59b672bdf8..c0cb086ced 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -60,7 +60,7 @@ export { // } // }); -export const VERSION = "1.448.1"; +export const VERSION = "1.454.1"; const command = new Command() .name("wmill") diff --git a/cli/metadata.ts b/cli/metadata.ts index 32cd556f76..e78b0bf539 100644 --- a/cli/metadata.ts +++ b/cli/metadata.ts @@ -49,7 +49,7 @@ import { FlowFile, replaceInlineScripts } from "./flow.ts"; import { getIsWin } from "./main.ts"; import { FlowValue } from "./gen/types.gen.ts"; -export async function generateAllMetadata() {} +export async function generateAllMetadata() { } function findClosestRawReqs( lang: "bun" | "python3" | "php" | undefined, @@ -104,7 +104,8 @@ export async function generateFlowLockInternal( folder: string, dryRun: boolean, workspace: Workspace, - justUpdateMetadataLock?: boolean + justUpdateMetadataLock?: boolean, + noStaleMessage?: boolean ): Promise { if (folder.endsWith(SEP)) { folder = folder.substring(0, folder.length - 1); @@ -112,7 +113,7 @@ export async function generateFlowLockInternal( const remote_path = folder .replaceAll(SEP, "/") .substring(0, folder.length - ".flow".length); - if (!justUpdateMetadataLock) { + if (!justUpdateMetadataLock && !noStaleMessage) { log.info(`Generating lock for flow ${folder} at ${remote_path}`); } @@ -120,9 +121,11 @@ export async function generateFlowLockInternal( const conf = await readLockfile(); if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) { - log.info( - colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`) - ); + if (!noStaleMessage) { + log.info( + colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`) + ); + } return; } else if (dryRun) { return remote_path; @@ -222,12 +225,7 @@ export async function generateScriptMetadataInternal( // read script content const scriptContent = await Deno.readTextFile(scriptPath); - let metadataContent = await Deno.readTextFile(metadataWithType.path); - const c = findCodebase(scriptPath, codebases); - - if (c) { - metadataContent += c.digest ?? ""; - } + const metadataContent = await Deno.readTextFile(metadataWithType.path); let hash = await generateScriptHash(rawReqs, scriptContent, metadataContent); @@ -261,7 +259,9 @@ export async function generateScriptMetadataInternal( } if (!opts.schemaOnly && !justUpdateMetadataLock) { - if (!c) { + const hasCodebase = findCodebase(scriptPath, codebases) != undefined; + + if (!hasCodebase) { await updateScriptLock( workspace, scriptContent, @@ -270,16 +270,13 @@ export async function generateScriptMetadataInternal( metadataParsedContent, rawReqs ); - metadataParsedContent.codebase = undefined; } else { - metadataParsedContent.codebase = c.digest; - metadataParsedContent.lock = ""; + metadataParsedContent.lock = ''; } } else { metadataParsedContent.lock = "!inline " + remotePath.replaceAll(SEP, "/") + ".script.lock"; } - let metaPath = remotePath + ".script.yaml"; let newMetadataContent = yamlStringify(metadataParsedContent, yamlOptions); if (metadataWithType.isJson) { @@ -287,10 +284,8 @@ export async function generateScriptMetadataInternal( newMetadataContent = JSON.stringify(metadataParsedContent); } - let metadataContentUsedForHash = newMetadataContent; - if (c) { - metadataContentUsedForHash += c.digest ?? ""; - } + const metadataContentUsedForHash = newMetadataContent; + hash = await generateScriptHash( rawReqs, scriptContent, @@ -318,9 +313,9 @@ export async function updateScriptSchema( path ); metadataContent.schema = result.schema; - if (result.has_preprocessor != null) + if (result.has_preprocessor == true) metadataContent.has_preprocessor = result.has_preprocessor; - if (result.no_main_func != null) + if (result.no_main_func === true) metadataContent.no_main_func = result.no_main_func; } @@ -390,7 +385,7 @@ async function updateScriptLock( if (await Deno.stat(lockPath)) { await Deno.remove(lockPath); } - } catch {} + } catch { } metadataContent.lock = ""; } } catch (e) { @@ -431,7 +426,7 @@ export async function updateFlow( } catch (e) { try { responseText = await rawResponse.text(); - } catch {} + } catch { } throw new Error( `Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}` ); @@ -528,8 +523,11 @@ export function inferSchema( }; } + if (!currentSchema) { + currentSchema = {} + } currentSchema.required = []; - const oldProperties = JSON.parse(JSON.stringify(currentSchema.properties)); + const oldProperties = JSON.parse(JSON.stringify(currentSchema?.properties ?? {})); currentSchema.properties = {}; for (const arg of inferedSchema.args) { @@ -576,23 +574,23 @@ export function argSigToJsonSchemaType( | string | { resource: string | null } | { - list: - | (string | { object: { key: string; typ: any }[] }) - | { str: any } - | { object: { key: string; typ: any }[] } - | null; - } + list: + | (string | { object: { key: string; typ: any }[] }) + | { str: any } + | { object: { key: string; typ: any }[] } + | null; + } | { dynselect: string } | { str: string[] | null } | { object: { key: string; typ: any }[] } | { - oneof: [ - { - label: string; - properties: { key: string; typ: any }[]; - } - ]; - }, + oneof: [ + { + label: string; + properties: { key: string; typ: any }[]; + } + ]; + }, oldS: SchemaProperty ): void { const newS: SchemaProperty = { type: "" }; @@ -795,10 +793,10 @@ export async function parseMetadataFile( scriptPath: string, generateMetadataIfMissing: | (GlobalOptions & { - path: string; - workspaceRemote: Workspace; - schemaOnly?: boolean; - }) + path: string; + workspaceRemote: Workspace; + schemaOnly?: boolean; + }) | undefined, globalDeps: GlobalDeps, codebases: SyncCodebase[] @@ -860,7 +858,9 @@ export async function parseMetadataFile( scriptInitialMetadata = (await yamlParseFile( metadataFilePath )) as ScriptMetadata; - replaceLock(scriptInitialMetadata); + if (!generateMetadataIfMissing.schemaOnly) { + replaceLock(scriptInitialMetadata); + } } catch (e) { log.info( colors.yellow( diff --git a/cli/script.ts b/cli/script.ts index c341489534..e6b08e15da 100644 --- a/cli/script.ts +++ b/cli/script.ts @@ -1,5 +1,5 @@ // deno-lint-ignore-file no-explicit-any -import { GlobalOptions, showDiff } from "./types.ts"; +import { GlobalOptions } from "./types.ts"; import { requireLogin, resolveWorkspace, validatePath } from "./context.ts"; import { colors, @@ -24,7 +24,6 @@ import { Workspace } from "./workspace.ts"; import { generateScriptMetadataInternal, parseMetadataFile, - updateScriptSchema, } from "./metadata.ts"; import { ScriptLanguage, @@ -34,6 +33,7 @@ import { elementsToMap, findCodebase, readDirRecursiveWithIgnore, + Skips, yamlOptions, } from "./sync.ts"; import { ignoreF } from "./sync.ts"; @@ -119,7 +119,7 @@ export async function findResourceFile(path: string) { if (validCandidates.length > 1) { throw new Error( "Found two resource files for the same resource" + - validCandidates.join(", ") + validCandidates.join(", ") ); } if (validCandidates.length < 1) { @@ -162,7 +162,7 @@ export async function handleFile( workspace: Workspace, alreadySynced: string[], message: string | undefined, - opts: (GlobalOptions & { defaultTs?: "bun" | "deno" }) | undefined, + opts: (GlobalOptions & { defaultTs?: "bun" | "deno" } & Skips) | undefined, globalDeps: GlobalDeps, codebases: SyncCodebase[] ): Promise { @@ -238,21 +238,22 @@ export async function handleFile( } log.info(`Finished building the bundle for ${path}`); } - const typed = ( - await parseMetadataFile( - remotePath, - opts - ? { + let typed = + opts?.skipScriptsMetadata ? undefined : + (await parseMetadataFile( + remotePath, + opts + ? { ...opts, path, workspaceRemote: workspace, schemaOnly: codebase ? true : undefined, } - : undefined, - globalDeps, - codebases - ) - )?.payload; + : undefined, + globalDeps, + codebases + ) + )?.payload; const workspaceId = workspace.workspaceId; @@ -268,20 +269,29 @@ export async function handleFile( } const content = await Deno.readTextFile(path); - if (codebase) { - const typedBefore = JSON.parse(JSON.stringify(typed.schema)); - await updateScriptSchema(content, language, typed, path); - if (typedBefore != typed.schema) { - log.info(`Updated metadata for bundle ${path}`); - showDiff( - yamlStringify(typedBefore, yamlOptions), - yamlStringify(typed.schema, yamlOptions) - ); - await Deno.writeTextFile( - remotePath + ".script.yaml", - yamlStringify(typed as Record, yamlOptions) - ); - } + if (opts?.skipScriptsMetadata) { + // if (codebase) { + // const typedBefore = JSON.parse(JSON.stringify(typed.schema)); + // await updateScriptSchema(content, language, typed, path); + // if (typedBefore != typed.schema) { + // log.info(`Updated metadata for bundle ${path}`); + // showDiff( + // yamlStringify(typedBefore, yamlOptions), + // yamlStringify(typed.schema, yamlOptions) + // ); + // await Deno.writeTextFile( + // remotePath + ".script.yaml", + // yamlStringify(typed as Record, yamlOptions) + // ); + // } + // } + // else { + typed = structuredClone(remote); + // } + } + + if (typed && codebase) { + typed.codebase = codebase.digest; } const requestBodyCommon: NewScript = { @@ -312,6 +322,8 @@ export async function handleFile( on_behalf_of_email: typed?.on_behalf_of_email, }; + // log.info(JSON.stringify(requestBodyCommon, null, 2)) + // log.info(JSON.stringify(opts, null, 2)) if (remote) { if (content === remote.content) { if ( @@ -327,19 +339,19 @@ export async function handleFile( deepEqual(typed.schema, remote.schema) && typed.tag == remote.tag && (typed.ws_error_handler_muted ?? false) == - remote.ws_error_handler_muted && + remote.ws_error_handler_muted && typed.dedicated_worker == remote.dedicated_worker && typed.cache_ttl == remote.cache_ttl && typed.concurrency_time_window_s == - remote.concurrency_time_window_s && + remote.concurrency_time_window_s && typed.concurrent_limit == remote.concurrent_limit && Boolean(typed.restart_unless_cancelled) == - Boolean(remote.restart_unless_cancelled) && + Boolean(remote.restart_unless_cancelled) && Boolean(typed.visible_to_runner_only) == - Boolean(remote.visible_to_runner_only) && + Boolean(remote.visible_to_runner_only) && Boolean(typed.no_main_func) == Boolean(remote.no_main_func) && Boolean(typed.has_preprocessor) == - Boolean(remote.has_preprocessor) && + Boolean(remote.has_preprocessor) && typed.priority == Boolean(remote.priority) && typed.timeout == remote.timeout && //@ts-ignore @@ -412,10 +424,9 @@ async function createScript( workspace: workspaceId, requestBody: body, }); - } catch (e) { + } catch (e: any) { throw Error( - `Script creation for ${body.path} with parent ${ - body.parent_hash + `Script creation for ${body.path} with parent ${body.parent_hash } was not successful: ${e.body ?? e.message}` ); } @@ -441,8 +452,7 @@ async function createScript( }); if (req.status != 201) { throw Error( - `Script snapshot creation was not successful: ${req.status} - ${ - req.statusText + `Script snapshot creation was not successful: ${req.status} - ${req.statusText } - ${await req.text()}` ); } @@ -453,8 +463,8 @@ export async function findContentFile(filePath: string) { const candidates = filePath.endsWith("script.json") ? exts.map((x) => filePath.replace(".script.json", x)) : filePath.endsWith("script.lock") - ? exts.map((x) => filePath.replace(".script.lock", x)) - : exts.map((x) => filePath.replace(".script.yaml", x)); + ? exts.map((x) => filePath.replace(".script.lock", x)) + : exts.map((x) => filePath.replace(".script.yaml", x)); const validCandidates = ( await Promise.all( @@ -473,7 +483,7 @@ export async function findContentFile(filePath: string) { if (validCandidates.length > 1) { throw new Error( "No content path given and more than one candidate found: " + - validCandidates.join(", ") + validCandidates.join(", ") ); } if (validCandidates.length < 1) { @@ -858,6 +868,7 @@ async function generateMetadata( } & SyncOptions, scriptPath: string | undefined ) { + log.info("This command only works for workspace scripts, for flows inline scripts use `wmill flow generate-locks`"); if (scriptPath == "") { scriptPath = undefined; } @@ -976,7 +987,7 @@ const command = new Command() .action(bootstrap as any) .command( "generate-metadata", - "re-generate the metadata file updating the lock and the script schema" + "re-generate the metadata file updating the lock and the script schema (for flows, use `wmill flow generate-locks`)" ) .arguments("[script:file]") .option("--yes", "Skip confirmation prompt") diff --git a/cli/sync.ts b/cli/sync.ts index 408c01884e..e8d6bb4d1a 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -101,8 +101,9 @@ async function addCodebaseDigestIfRelevant( return content; } let isTs = true; + const replacedPath = path.replace(".script.yaml", ".ts"); try { - await Deno.stat(path.replace(".script.yaml", ".ts")); + await Deno.stat(replacedPath); } catch { isTs = false; } @@ -110,12 +111,12 @@ async function addCodebaseDigestIfRelevant( return content; } if (isTs) { - const c = findCodebase(path, codebases); + const c = findCodebase(replacedPath, codebases); if (c) { const parsed: any = yamlParseContent(path, content); if (parsed && typeof parsed == "object") { parsed["codebase"] = c.digest; - parsed["lock"] = undefined; + parsed["lock"] = ''; return yamlStringify(parsed, yamlOptions); } else { throw Error( @@ -158,9 +159,8 @@ export async function FSFSElement( // }, async getContentText(): Promise { const content = await Deno.readTextFile(localP); - - const r = await addCodebaseDigestIfRelevant(localP, content, codebases); - // console.log(r); + const itemPath = localP.substring(p.length + 1) + const r = await addCodebaseDigestIfRelevant(itemPath, content, codebases); return r; }, }; @@ -352,12 +352,12 @@ function ZipFSElement( ) ? "flow" : p.endsWith("app.json") - ? "app" - : p.endsWith("script.json") - ? "script" - : p.endsWith("resource.json") - ? "resource" - : "other"; + ? "app" + : p.endsWith("script.json") + ? "script" + : p.endsWith("resource.json") + ? "resource" + : "other"; const isJson = p.endsWith(".json"); @@ -387,7 +387,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, s.path), - async *getChildren() {}, + async *getChildren() { }, // deno-lint-ignore require-await async getContentText() { return s.content; @@ -398,7 +398,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, "flow.yaml"), - async *getChildren() {}, + async *getChildren() { }, // deno-lint-ignore require-await async getContentText() { return yamlStringify(flow, yamlOptions); @@ -414,7 +414,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, s.path), - async *getChildren() {}, + async *getChildren() { }, // deno-lint-ignore require-await async getContentText() { return s.content; @@ -425,7 +425,7 @@ function ZipFSElement( yield { isDirectory: false, path: path.join(finalPath, "app.yaml"), - async *getChildren() {}, + async *getChildren() { }, // deno-lint-ignore require-await async getContentText() { return yamlStringify(app, yamlOptions); @@ -490,7 +490,7 @@ function ZipFSElement( r.push({ isDirectory: false, path: removeSuffix(finalPath, ".json") + ".lock", - async *getChildren() {}, + async *getChildren() { }, // deno-lint-ignore require-await async getContentText() { return lock; @@ -513,7 +513,7 @@ function ZipFSElement( removeSuffix(finalPath, ".resource.json") + ".resource.file." + formatExtension, - async *getChildren() {}, + async *getChildren() { }, // deno-lint-ignore require-await async getContentText() { return fileContent; @@ -574,19 +574,19 @@ export async function* readDirRecursiveWithIgnore( // getContentBytes(): Promise; getContentText(): Promise; }[] = [ - { - path: root.path, - ignored: ignore(root.path, root.isDirectory), - isDirectory: root.isDirectory, - c: root.getChildren, - // getContentBytes(): Promise { - // throw undefined; - // }, - getContentText(): Promise { - throw undefined; + { + path: root.path, + ignored: ignore(root.path, root.isDirectory), + isDirectory: root.isDirectory, + c: root.getChildren, + // getContentBytes(): Promise { + // throw undefined; + // }, + getContentText(): Promise { + throw undefined; + }, }, - }, - ]; + ]; while (stack.length > 0) { const e = stack.pop()!; @@ -606,7 +606,7 @@ export async function* readDirRecursiveWithIgnore( type Added = { name: "added"; path: string; content: string }; type Deleted = { name: "deleted"; path: string }; -type Edit = { name: "edited"; path: string; before: string; after: string }; +type Edit = { name: "edited"; path: string; before: string; after: string; codebase?: string }; type Change = Added | Deleted | Edit; @@ -649,7 +649,7 @@ export async function elementsToMap( "js", "lock", "rs", - "cs", + "cs", "yml", ].includes(path.split(".").pop() ?? "") && !isFileResource(path) @@ -677,10 +677,11 @@ export async function elementsToMap( return map; } -interface Skips { +export interface Skips { skipVariables?: boolean | undefined; skipResources?: boolean | undefined; skipSecrets?: boolean | undefined; + skipScriptsMetadata?: boolean | undefined; includeSchedules?: boolean | undefined; includeUsers?: boolean | undefined; includeGroups?: boolean | undefined; @@ -694,13 +695,14 @@ async function compareDynFSElement( ignore: (path: string, isDirectory: boolean) => boolean, json: boolean, skips: Skips, - ignoreMetadataDeletion: boolean + ignoreMetadataDeletion: boolean, + codebases: SyncCodebase[] ): Promise { const [m1, m2] = els2 ? await Promise.all([ - elementsToMap(els1, ignore, json, skips), - elementsToMap(els2, ignore, json, skips), - ]) + elementsToMap(els1, ignore, json, skips), + elementsToMap(els2, ignore, json, skips), + ]) : [await elementsToMap(els1, ignore, json, skips), {}]; const changes: Change[] = []; @@ -734,25 +736,81 @@ async function compareDynFSElement( return yamlParseContent(k, v); } } - for (const [k, v] of Object.entries(m1)) { + const codebaseChanges: Record = {}; + for (let [k, v] of Object.entries(m1)) { + const isScriptMetadata = k.endsWith(".script.yaml") || k.endsWith(".script.json"); + const skipMetadata = skips.skipScriptsMetadata && isScriptMetadata; + if (m2[k] === undefined) { + if (skipMetadata) { + continue; + } changes.push({ name: "added", path: k, content: v }); - } else if ( - m2[k] != v && - (!k.endsWith(".json") || !deepEqual(JSON.parse(v), JSON.parse(m2[k]))) && - (!k.endsWith(".yaml") || !deepEqual(parseYaml(k, v), parseYaml(k, m2[k]))) - ) { - changes.push({ name: "edited", path: k, after: v, before: m2[k] }); + } else { + if (m2[k] == v) { + continue; + } + else if (k.endsWith(".json")) { + if (deepEqual(JSON.parse(v), JSON.parse(m2[k]))) { + continue; + } + } else if (k.endsWith(".yaml")) { + const before = parseYaml(k, m2[k]); + const after = parseYaml(k, v); + if (deepEqual(before, after)) { + continue; + } + if (before.codebase != undefined) { + delete before.codebase; + m2[k] = yamlStringify(before, yamlOptions); + } + if (after.codebase != undefined) { + if (before.codebase != after.codebase) { + codebaseChanges[k] = after.codebase; + } + delete after.codebase; + v = yamlStringify(after, yamlOptions); + } + if (skipMetadata) { + continue; + } + } + changes.push({ name: "edited", path: k, after: v, before: m2[k], codebase: codebaseChanges[k] }); } } + const remoteCodebase: Record = {}; for (const [k] of Object.entries(m2)) { - if ( - m1[k] === undefined && - (!ignoreMetadataDeletion || - (!k?.endsWith(".script.yaml") && !k?.endsWith(".script.json"))) - ) { - changes.push({ name: "deleted", path: k }); + if (m1[k] === undefined) { + if (!ignoreMetadataDeletion || (!k?.endsWith(".script.yaml") && !k?.endsWith(".script.json"))) { + changes.push({ name: "deleted", path: k }); + } else if (k?.endsWith(".script.yaml")) { + let o = parseYaml(k, m2[k]); + if (o.codebase != undefined) { + remoteCodebase[k] = o.codebase; + } + } + } + } + + for (const [k, v] of Object.entries(remoteCodebase)) { + const tsFile = k.replace(".script.yaml", ".ts"); + if (changes.find(c => c.path == tsFile && (c.name == "edited" || c.name == "deleted"))) { + continue; + } + let c = findCodebase(tsFile, codebases); + if (c?.digest != v) { + changes.push({ name: "edited", path: tsFile, codebase: v, before: m1[tsFile], after: m2[tsFile] }); + } + } + + for (const change of changes) { + const codebase = codebaseChanges[change.path]; + if (!codebase) continue; + + const tsFile = change.path.replace(".script.yaml", ".ts"); + if (change.name == "edited" && change.path == tsFile) { + change.codebase = codebase; } } @@ -887,6 +945,58 @@ export async function ignoreF(wmillconf: { }; } +interface ChangeTracker { + scripts: string[]; + flows: string[]; + apps: string[]; +} + +// deno-lint-ignore no-inner-declarations +async function addToChangedIfNotExists(p: string, tracker: ChangeTracker) { + const isScript = exts.some((e) => p.endsWith(e)); + if (isScript) { + if (p.includes(".flow" + SEP)) { + const folder = + p.substring(0, p.indexOf(".flow" + SEP)) + ".flow" + SEP; + if (!tracker.flows.includes(folder)) { + tracker.flows.push(folder); + } + } else if (p.includes(".app" + SEP)) { + const folder = p.substring(0, p.indexOf(".app" + SEP)) + ".app" + SEP; + if (!tracker.apps.includes(folder)) { + tracker.apps.push(folder); + } + } else { + if (!tracker.scripts.includes(p)) { + tracker.scripts.push(p); + } + } + } else if (p.endsWith(".script.yaml") || p.endsWith(".script.json")) { + try { + const contentPath = await findContentFile(p); + if (!contentPath) return; + if (tracker.scripts.includes(contentPath)) return; + tracker.scripts.push(contentPath); + } catch { + // ignore + } + } +} + +async function buildTracker(changes: Change[]) { + const tracker: ChangeTracker = { + scripts: [], + flows: [], + apps: [], + }; + for (const change of changes) { + if (change.name == "added" || change.name == "edited") { + await addToChangedIfNotExists(change.path, tracker); + } + } + return tracker; +} + export async function pull(opts: GlobalOptions & SyncOptions) { opts = await mergeConfigWithConfigFile(opts); @@ -940,7 +1050,8 @@ export async function pull(opts: GlobalOptions & SyncOptions) { await ignoreF(opts), opts.json ?? false, opts, - false + false, + codebases ); log.info( @@ -959,41 +1070,8 @@ export async function pull(opts: GlobalOptions & SyncOptions) { } const conflicts = []; - const changedScripts: string[] = []; - const changedFlows: string[] = []; - const changedApps: string[] = []; - // deno-lint-ignore no-inner-declarations - async function addToChangedIfNotExists(p: string) { - const isScript = exts.some((e) => p.endsWith(e)); - if (isScript) { - if (p.includes(".flow" + SEP)) { - const folder = - p.substring(0, p.indexOf(".flow" + SEP)) + ".flow" + SEP; - if (!changedFlows.includes(folder)) { - changedFlows.push(folder); - } - } else if (p.includes(".app" + SEP)) { - const folder = p.substring(0, p.indexOf(".app" + SEP)) + ".app" + SEP; - if (!changedApps.includes(folder)) { - changedApps.push(folder); - } - } else { - if (!changedScripts.includes(p)) { - changedScripts.push(p); - } - } - } else if (p.endsWith(".script.yaml") || p.endsWith(".script.json")) { - try { - const contentPath = await findContentFile(p); - if (!contentPath) return; - if (changedScripts.includes(contentPath)) return; - changedScripts.push(contentPath); - } catch { - // ignore - } - } - } + log.info(colors.gray(`Applying changes to files ...`)); for await (const change of changes) { @@ -1054,7 +1132,6 @@ export async function pull(opts: GlobalOptions & SyncOptions) { await ensureDir(path.dirname(stateTarget)); await Deno.copyFile(target, stateTarget); } - await addToChangedIfNotExists(change.path); } else if (change.name === "added") { await ensureDir(path.dirname(target)); if (opts.stateful) { @@ -1066,7 +1143,6 @@ export async function pull(opts: GlobalOptions & SyncOptions) { if (opts.stateful) { await Deno.copyFile(target, stateTarget); } - await addToChangedIfNotExists(change.path); } else if (change.name === "deleted") { try { log.info( @@ -1103,7 +1179,9 @@ export async function pull(opts: GlobalOptions & SyncOptions) { const globalDeps = await findGlobalDeps(); - for (const change of changedScripts) { + const tracker: ChangeTracker = await buildTracker(changes); + + for (const change of tracker.scripts) { await generateScriptMetadataInternal( change, workspace, @@ -1115,13 +1193,13 @@ export async function pull(opts: GlobalOptions & SyncOptions) { true ); } - for (const change of changedFlows) { + for (const change of tracker.flows) { log.info(`Updating lock for flow ${change}`); await generateFlowLockInternal(change, false, workspace, true); } - if (changedApps.length > 0) { + if (tracker.apps.length > 0) { log.info( - `Apps ${changedApps.join( + `Apps ${tracker.apps.join( ", " )} scripts were changed but ignoring for now` ); @@ -1146,9 +1224,11 @@ function prettyChanges(changes: Change[]) { ); } else if (change.name === "edited") { log.info( - colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path) + colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path + (change.codebase ? ` (codebase changed)` : "")) ); - showDiff(change.before, change.after); + if (change.before != change.after) { + showDiff(change.before, change.after); + } } } } @@ -1238,9 +1318,58 @@ export async function push(opts: GlobalOptions & SyncOptions) { await ignoreF(opts), opts.json ?? false, opts, - true + true, + codebases ); + + const globalDeps = await findGlobalDeps(); + + const tracker: ChangeTracker = await buildTracker(changes); + + const staleScripts: string[] = []; + const staleFlows: string[] = []; + for (const change of tracker.scripts) { + const stale = await generateScriptMetadataInternal( + change, + workspace, + opts, + true, + true, + globalDeps, + codebases, + false + ); + if (stale) { + staleScripts.push(stale); + } + } + + if (staleScripts.length > 0) { + log.info("") + log.warn("Stale scripts metadata found, you may want to update them using 'wmill script generate-metadata' before pushing:"); + for (const stale of staleScripts) { + log.warn(stale); + } + + log.info("") + } + + for (const change of tracker.flows) { + const stale = await generateFlowLockInternal(change, true, workspace, false, true); + if (stale) { + staleFlows.push(stale); + } + } + + if (staleFlows.length > 0) { + log.warn("Stale flows locks found, you may want to update them using 'wmill flow generate-locks' before pushing:"); + for (const stale of staleFlows) { + log.warn(stale); + } + log.info("") + } + const version = await fetchVersion(workspace.remote); log.info(colors.gray("Remote version: " + version)); @@ -1264,7 +1393,9 @@ export async function push(opts: GlobalOptions & SyncOptions) { log.info(colors.gray(`Applying changes to files ...`)); const alreadySynced: string[] = []; - const globalDeps = await findGlobalDeps(); + + + for await (const change of changes) { const stateTarget = path.join(Deno.cwd(), ".wmill", change.path); @@ -1512,24 +1643,13 @@ const command = new Command() ) .command("pull") .description("Pull any remote changes and apply them locally.") - .option( - "--fail-conflicts", - "Error on conflicts (both remote and local have changes on the same item)" - ) - .option( - "--raw", - "Push without using state, just overwrite. (Default, has no effect)" - ) .option("--yes", "Pull without needing confirmation") - .option( - "--stateful", - "Pull using state tracking (create .wmill folder and needed for --fail-conflicts)" - ) .option("--plain-secrets", "Pull secrets as plain text") .option("--json", "Use JSON instead of YAML") .option("--skip-variables", "Skip syncing variables (including secrets)") .option("--skip-secrets", "Skip syncing only secrets variables") .option("--skip-resources", "Skip syncing resources") + // .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic") .option("--include-schedules", "Include syncing schedules") .option("--include-users", "Include syncing users") .option("--include-groups", "Include syncing groups") @@ -1551,25 +1671,13 @@ const command = new Command() .action(pull as any) .command("push") .description("Push any local changes and apply them remotely.") - .option( - "--fail-conflicts", - "Error on conflicts (both remote and local have changes on the same item)" - ) - .option( - "--raw", - "Push without using state, just overwrite. (Default, has no effect)" - ) - .option( - "--stateful", - "Pull using state tracking (use .wmill folder and needed for --fail-conflicts)w" - ) - .option("--skip-pull", "(stateful only) Push without pulling first") .option("--yes", "Push without needing confirmation") .option("--plain-secrets", "Push secrets as plain text") .option("--json", "Use JSON instead of YAML") .option("--skip-variables", "Skip syncing variables (including secrets)") .option("--skip-secrets", "Skip syncing only secrets variables") .option("--skip-resources", "Skip syncing resources") + // .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic") .option("--include-schedules", "Include syncing schedules") .option("--include-users", "Include syncing users") .option("--include-groups", "Include syncing groups") diff --git a/cli/types.ts b/cli/types.ts index 0d717c296e..0ba5a1c476 100644 --- a/cli/types.ts +++ b/cli/types.ts @@ -79,7 +79,8 @@ export function showDiff(local: string, remote: string) { log.info("Diff too large to display"); return; } - for (const part of Diff.diffLines(local, remote)) { + + for (const part of Diff.diffLines(local ?? '', remote ?? '')) { if (part.removed) { // print red if removed without newline finalString += `\x1b[31m${part.value}\x1b[0m`; @@ -157,8 +158,8 @@ export function parseFromPath(p: string, content: string): any { return p.endsWith(".yaml") ? yamlParseContent(p, content) : p.endsWith(".json") - ? JSON.parse(content) - : content; + ? JSON.parse(content) + : content; } export function parseFromFile(p: string): any { if (p.endsWith(".json")) { diff --git a/deno-client/.gitignore b/deno-client/.gitignore deleted file mode 100644 index ea0b8f9eaa..0000000000 --- a/deno-client/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -windmill-api/ -node_modules diff --git a/deno-client/.vscode/settings.json b/deno-client/.vscode/settings.json deleted file mode 100644 index 52ca21d5c4..0000000000 --- a/deno-client/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "deno.enable": true, - "deno.lint": true, - "deno.unstable": true -} diff --git a/deno-client/README.md b/deno-client/README.md deleted file mode 100644 index fc137bc4c0..0000000000 --- a/deno-client/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# windmill-deno-client - -Deno client for Windmill - -## Howto build locally - -run `./build.sh` diff --git a/deno-client/build.sh b/deno-client/build.sh deleted file mode 100755 index 37bac778b6..0000000000 --- a/deno-client/build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -e - -npm ci --ignore-scripts -npx --yes openapi-typescript-codegen --input ../backend/windmill-api/openapi.yaml \ - --output ./src --useOptions \ - && sed -i '213 i \\ request.referrerPolicy = \"no-referrer\"\n' src/core/request.ts - npx --yes denoify -rm -rf windmill-api -mv deno_dist windmill-api -sed -i 's/buffer DENOIFY: UNKNOWN NODE BUILTIN/node:buffer/' ./windmill-api/core/request.ts -rm -rf src/ diff --git a/deno-client/mod.ts b/deno-client/mod.ts deleted file mode 100644 index d29cc8881c..0000000000 --- a/deno-client/mod.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { - JobService, - ResourceService, - VariableService, -} from "./windmill-api/index.ts"; -import { OpenAPI } from "./windmill-api/index.ts"; - -export { - AdminService, - AuditService, - FlowService, - GranularAclService, - GroupService, - JobService, - ResourceService, - VariableService, - ScriptService, - ScheduleService, - SettingsService, - UserService, - WorkspaceService, -} from "./windmill-api/index.ts"; - -export type Sql = string; -export type Email = string; -export type Base64 = string; -export type Resource = any; - -export const SHARED_FOLDER = "/shared"; - -export function setClient(token: string, baseUrl: string) { - OpenAPI.WITH_CREDENTIALS = true; - OpenAPI.TOKEN = token; - OpenAPI.BASE = baseUrl + "/api"; -} - -setClient( - Deno.env.get("WM_TOKEN") ?? "no_token", - Deno.env.get("BASE_INTERNAL_URL") ?? - Deno.env.get("BASE_URL") ?? - "http://localhost:8000" -); - -/** - * Create a client configuration from env variables - * @returns client configuration - */ -export function getWorkspace(): string { - return Deno.env.get("WM_WORKSPACE") ?? "no_workspace"; -} - -/** - * Get a resource value by path - * @param path path of the resource, default to internal state path - * @param undefinedIfEmpty if the resource does not exist, return undefined instead of throwing an error - * @returns resource value - */ -export async function getResource( - path?: string, - undefinedIfEmpty?: boolean -): Promise { - const workspace = getWorkspace(); - path = path ?? getStatePath(); - try { - return await ResourceService.getResourceValueInterpolated({ - workspace, - path, - }); - } catch (e: any) { - if (undefinedIfEmpty && e.status === 404) { - return undefined; - } else { - throw Error(`Resource not found at ${path} or not visible to you`); - } - } -} - -/** - * Resolve a resource value in case the default value was picked because the input payload was undefined - * @param obj resource value or path of the resource under the format `$res:path` - * @returns resource value - */ -export async function resolveDefaultResource(obj: any): Promise { - if (typeof obj === "string" && obj.startsWith("$res:")) { - return await getResource(obj.substring(5), true); - } else { - return obj; - } -} - -export function getStatePath(): string { - const state_path = Deno.env.get("WM_STATE_PATH_NEW") ?? Deno.env.get("WM_STATE_PATH"); - if (state_path === undefined) { - throw Error("State path not set"); - } - return state_path; -} - -/** - * Set a resource value by path - * @param value new value of the resource to set - * @param path path of the resource to set, default to state path - * @param initializeToTypeIfNotExist if the resource does not exist, initialize it with this type - */ -export async function setResource( - value: any, - path?: string, - initializeToTypeIfNotExist?: string -): Promise { - path = path ?? getStatePath(); - const workspace = getWorkspace(); - if (await ResourceService.existsResource({ workspace, path })) { - await ResourceService.updateResourceValue({ - workspace, - path, - requestBody: { value }, - }); - } else if (initializeToTypeIfNotExist) { - await ResourceService.createResource({ - workspace, - requestBody: { path, value, resource_type: initializeToTypeIfNotExist }, - }); - } else { - throw Error( - `Resource at path ${path} does not exist and no type was provided to initialize it` - ); - } -} - -/** - * Set the state - * @param state state to set - * @deprecated use setState instead - */ -export async function setInternalState(state: any): Promise { - await setResource(state, undefined, "state"); -} - -/** - * Set the state - * @param state state to set - */ -export async function setState(state: any): Promise { - await setResource(state, undefined, "state"); -} - -/** - * Set the shared state - * @param state state to set - */ -export async function setSharedState( - state: any, - path = "state.json" -): Promise { - await Deno.writeTextFile(SHARED_FOLDER + "/" + path, JSON.stringify(state)); -} - -/** - * Get the shared state - * @param state state to set - */ -export async function getSharedState(path = "state.json"): Promise { - return JSON.parse(await Deno.readTextFile(SHARED_FOLDER + "/" + path)); -} - -/** - * Get the internal state - * @deprecated use getState instead - */ -export async function getInternalState(): Promise { - return await getResource(getStatePath(), true); -} - -/** - * Get the state shared across executions - */ -export async function getState(): Promise { - return await getResource(getStatePath(), true); -} - -/** - * Get a variable by path - * @param path path of the variable - * @returns variable value - */ -export async function getVariable(path: string): Promise { - const workspace = getWorkspace(); - try { - return await VariableService.getVariableValue({ workspace, path }); - } catch (e: any) { - throw Error(`Variable not found at ${path} or not visible to you`); - } -} - -/** - * Set a variable by path, create if not exist - * @param path path of the variable - * @param value value of the variable - * @param isSecretIfNotExist if the variable does not exist, create it as secret or not (default: false) - * @param descriptionIfNotExist if the variable does not exist, create it with this description (default: "") - */ -export async function setVariable( - path: string, - value: string, - isSecretIfNotExist?: boolean, - descriptionIfNotExist?: string -): Promise { - const workspace = getWorkspace(); - if (await VariableService.existsVariable({ workspace, path })) { - await VariableService.updateVariable({ - workspace, - path, - requestBody: { value }, - }); - } else { - await VariableService.createVariable({ - workspace, - requestBody: { - path, - value, - is_secret: isSecretIfNotExist ?? false, - description: descriptionIfNotExist ?? "", - }, - }); - } -} - -export async function databaseUrlFromResource(path: string): Promise { - const resource = await getResource(path); - return `postgresql://${resource.user}:${resource.password}@${resource.host}:${resource.port}/${resource.dbname}?sslmode=${resource.sslmode}`; -} - -/** - * Get URLs needed for resuming a flow after this step - * @param approver approver name - * @returns approval page UI URL, resume and cancel API URLs for resumeing the flow - */ -export async function getResumeUrls(approver?: string): Promise<{ - approvalPage: string; - resume: string; - cancel: string; -}> { - const nonce = Math.floor(Math.random() * 4294967295); - const workspace = getWorkspace(); - return await JobService.getResumeUrls({ - workspace, - resumeId: nonce, - approver, - id: Deno.env.get("WM_JOB_ID") ?? "NO_JOB_ID", - }); -} - -/** - * @deprecated use getResumeUrls instead - */ -export function getResumeEndpoints(approver?: string): Promise<{ - approvalPage: string; - resume: string; - cancel: string; -}> { - return getResumeUrls(approver); -} - -export function base64ToUint8Array(data: string): Uint8Array { - return Uint8Array.from(atob(data), (c) => c.charCodeAt(0)); -} - -export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string { - let base64 = ""; - const encodings = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - const bytes = new Uint8Array(arrayBuffer); - const byteLength = bytes.byteLength; - const byteRemainder = byteLength % 3; - const mainLength = byteLength - byteRemainder; - - let a, b, c, d; - let chunk; - - // Main loop deals with bytes in chunks of 3 - for (let i = 0; i < mainLength; i = i + 3) { - // Combine the three bytes into a single integer - chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; - - // Use bitmasks to extract 6-bit segments from the triplet - a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 - b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 - c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 - d = chunk & 63; // 63 = 2^6 - 1 - - // Convert the raw binary segments to the appropriate ASCII encoding - base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; - } - - // Deal with the remaining bytes and padding - if (byteRemainder == 1) { - chunk = bytes[mainLength]; - - a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 - - // Set the 4 least significant bits to zero - b = (chunk & 3) << 4; // 3 = 2^2 - 1 - - base64 += encodings[a] + encodings[b] + "=="; - } else if (byteRemainder == 2) { - chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]; - - a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 - b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 - - // Set the 2 least significant bits to zero - c = (chunk & 15) << 2; // 15 = 2^4 - 1 - - base64 += encodings[a] + encodings[b] + encodings[c] + "="; - } - - return base64; -} diff --git a/deno-client/package-lock.json b/deno-client/package-lock.json deleted file mode 100644 index fba5fea4d7..0000000000 --- a/deno-client/package-lock.json +++ /dev/null @@ -1,1512 +0,0 @@ -{ - "name": "deno-client", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "dependencies": { - "denoify": "^1.3.1", - "openapi-typescript-codegen": "^0.23.0" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.6", - "call-me-maybe": "^1.0.1", - "js-yaml": "^4.1.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" - }, - "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dependencies": { - "@octokit/types": "^6.0.3" - } - }, - "node_modules/@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", - "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", - "dependencies": { - "@octokit/types": "^6.40.0" - }, - "peerDependencies": { - "@octokit/core": ">=2" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "5.16.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", - "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", - "dependencies": { - "@octokit/types": "^6.39.0", - "deprecation": "^2.3.1" - }, - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "node_modules/@octokit/rest": { - "version": "18.12.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", - "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", - "dependencies": { - "@octokit/core": "^3.5.1", - "@octokit/plugin-paginate-rest": "^2.16.8", - "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^5.12.0" - } - }, - "node_modules/@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "dependencies": { - "@octokit/openapi-types": "^12.11.0" - } - }, - "node_modules/@types/comment-json": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/comment-json/-/comment-json-1.1.1.tgz", - "integrity": "sha512-U70oEqvnkeSSp8BIJwJclERtT13rd9ejK7XkIzMCQQePZe3VW1b7iQggXyW4ZvfGtGeXD0pZw24q5iWNe++HqQ==" - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "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==" - }, - "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" - }, - "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/call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/comment-json": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-3.0.3.tgz", - "integrity": "sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg==", - "dependencies": { - "core-util-is": "^1.0.2", - "esprima": "^4.0.1", - "has-own-prop": "^2.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "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==" - }, - "node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/denoify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/denoify/-/denoify-1.3.1.tgz", - "integrity": "sha512-5wV8Akq7c14CUQggl6w4ar88W6ZBhGhCRhFGktYM0qUFFO6FkCp/kDc9LQgLEaV9TX4l6UQU7ueMAGgcUB4lvA==", - "dependencies": { - "@octokit/rest": "^18.0.0", - "@types/comment-json": "^1.1.1", - "commander": "^4.1.1", - "comment-json": "^3.0.2", - "cosmiconfig": "^7.0.1", - "evt": "^2.4.6", - "get-github-default-branch-name": "^0.0.4", - "gitignore-parser": "0.0.2", - "glob": "^7.1.6", - "minimal-polyfills": "^2.2.2", - "node-fetch": "^2.6.7", - "path-depth": "^1.0.0", - "scripting-tools": "^0.19.14", - "tsafe": "^1.1.3", - "url-join": "^4.0.1", - "yaml": "^2.1.1" - }, - "bin": { - "denoify": "bin/denoify.js", - "enable_short_npm_import_path": "bin/enable_short_npm_import_path.js", - "remove_deno_dist_from_gitignore": "bin/remove_deno_dist_from_gitignore.js" - } - }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/evt": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/evt/-/evt-2.4.6.tgz", - "integrity": "sha512-4kSu9YVklCeddEAu/UCzIISrxc5SAD82hX7gXjBox4yyrPNblYrmJr4GgTOOQpNXTGYci+UIpFUYikgZa9fyTQ==", - "dependencies": { - "minimal-polyfills": "^2.2.2", - "run-exclusive": "^2.2.16", - "tsafe": "^1.1.3" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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==" - }, - "node_modules/get-github-default-branch-name": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/get-github-default-branch-name/-/get-github-default-branch-name-0.0.4.tgz", - "integrity": "sha512-ltOGC9Jk0k8boe48Gk7SkJErwxt7MhwXtbNrBUyNCZcwcXSmGRdkKb2u0YO250PGvPsUtdqRjg7lVuIk1VtpCg==", - "dependencies": { - "@octokit/rest": "^18.0.0", - "scripting-tools": "^0.19.12" - } - }, - "node_modules/gitignore-parser": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/gitignore-parser/-/gitignore-parser-0.0.2.tgz", - "integrity": "sha512-X6mpqUv59uWLGD4n3hZ8Cu8KbF2PMWPSFYmxZjdkpm3yOU7hSUYnzTkZI1mcWqchphvqyuz3/BhgBR4E/JtkCg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-own-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", - "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "9.0.9" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/minimal-polyfills": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/minimal-polyfills/-/minimal-polyfills-2.2.2.tgz", - "integrity": "sha512-eEOUq/LH/DbLWihrxUP050Wi7H/N/I2dQT98Ep6SqOpmIbk4sXOI4wqalve66QoZa+6oljbZWU6I6T4dehQGmw==" - }, - "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/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "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/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openapi-typescript-codegen": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.23.0.tgz", - "integrity": "sha512-gOJXy5g3H3HlLpVNN+USrNK2i2KYBmDczk9Xk34u6JorwrGiDJZUj+al4S+i9TXdfUQ/ZaLxE59Xf3wqkxGfqA==", - "dependencies": { - "camelcase": "^6.3.0", - "commander": "^9.3.0", - "fs-extra": "^10.1.0", - "handlebars": "^4.7.7", - "json-schema-ref-parser": "^9.0.9" - }, - "bin": { - "openapi": "bin/index.js" - } - }, - "node_modules/openapi-typescript-codegen/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==", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-depth": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-depth/-/path-depth-1.0.0.tgz", - "integrity": "sha512-dEiwdXAQyLvOi6ktLqhFhjVelJiVsdp2xBX3BaUtYCCkMRZTwUiq7cha+A0myvAVXRHbXfjhfTf4mNoAWzm2iA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/run-exclusive": { - "version": "2.2.17", - "resolved": "https://registry.npmjs.org/run-exclusive/-/run-exclusive-2.2.17.tgz", - "integrity": "sha512-RM/mJJuX0VVfkiCa/yWJLs8Tb2OTweRaKEbWtVj77jXz94ztheIFnfLtK91u7XY5iy4/tyVIXEdv+kQmC1E51w==", - "dependencies": { - "minimal-polyfills": "^2.2.1" - } - }, - "node_modules/scripting-tools": { - "version": "0.19.14", - "resolved": "https://registry.npmjs.org/scripting-tools/-/scripting-tools-0.19.14.tgz", - "integrity": "sha512-KGRES70dEmcaCdpx3R88bLWmfA4mQ/EGikCQy0FGTZwx3y9F5yYkzEhwp02+ZTgpvF25JcNOhDBbOqL6z92kwg==" - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/tsafe": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tsafe/-/tsafe-1.1.3.tgz", - "integrity": "sha512-QQQoed5Acb7Qe/sjMwE/qg57mxD5MXqY5HcGN4i3QyhJiplpo79ABEZiOBvL1cRTOYIzeryWvWQ3xsotTA49MQ==" - }, - "node_modules/uglify-js": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.3.tgz", - "integrity": "sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg==", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" - }, - "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/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/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", - "engines": { - "node": ">= 14" - } - } - }, - "dependencies": { - "@apidevtools/json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", - "requires": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.6", - "call-me-maybe": "^1.0.1", - "js-yaml": "^4.1.0" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" - }, - "@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "requires": { - "@octokit/types": "^6.0.3" - } - }, - "@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "requires": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "requires": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "requires": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" - }, - "@octokit/plugin-paginate-rest": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", - "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", - "requires": { - "@octokit/types": "^6.40.0" - } - }, - "@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "requires": {} - }, - "@octokit/plugin-rest-endpoint-methods": { - "version": "5.16.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", - "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", - "requires": { - "@octokit/types": "^6.39.0", - "deprecation": "^2.3.1" - } - }, - "@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "requires": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "@octokit/rest": { - "version": "18.12.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", - "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", - "requires": { - "@octokit/core": "^3.5.1", - "@octokit/plugin-paginate-rest": "^2.16.8", - "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^5.12.0" - } - }, - "@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "requires": { - "@octokit/openapi-types": "^12.11.0" - } - }, - "@types/comment-json": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/comment-json/-/comment-json-1.1.1.tgz", - "integrity": "sha512-U70oEqvnkeSSp8BIJwJclERtT13rd9ejK7XkIzMCQQePZe3VW1b7iQggXyW4ZvfGtGeXD0pZw24q5iWNe++HqQ==" - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" - }, - "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" - } - }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==" - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "comment-json": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-3.0.3.tgz", - "integrity": "sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg==", - "requires": { - "core-util-is": "^1.0.2", - "esprima": "^4.0.1", - "has-own-prop": "^2.0.0", - "repeat-string": "^1.6.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "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==" - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "dependencies": { - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - } - } - }, - "denoify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/denoify/-/denoify-1.3.1.tgz", - "integrity": "sha512-5wV8Akq7c14CUQggl6w4ar88W6ZBhGhCRhFGktYM0qUFFO6FkCp/kDc9LQgLEaV9TX4l6UQU7ueMAGgcUB4lvA==", - "requires": { - "@octokit/rest": "^18.0.0", - "@types/comment-json": "^1.1.1", - "commander": "^4.1.1", - "comment-json": "^3.0.2", - "cosmiconfig": "^7.0.1", - "evt": "2.4.6", - "get-github-default-branch-name": "^0.0.4", - "gitignore-parser": "0.0.2", - "glob": "^7.1.6", - "minimal-polyfills": "^2.2.2", - "node-fetch": "^2.6.7", - "path-depth": "^1.0.0", - "scripting-tools": "^0.19.14", - "tsafe": "^1.1.3", - "url-join": "^4.0.1", - "yaml": "^2.1.1" - } - }, - "deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "evt": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/evt/-/evt-2.4.6.tgz", - "integrity": "sha512-4kSu9YVklCeddEAu/UCzIISrxc5SAD82hX7gXjBox4yyrPNblYrmJr4GgTOOQpNXTGYci+UIpFUYikgZa9fyTQ==", - "requires": { - "minimal-polyfills": "^2.2.2", - "run-exclusive": "^2.2.16", - "tsafe": "^1.1.3" - } - }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "get-github-default-branch-name": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/get-github-default-branch-name/-/get-github-default-branch-name-0.0.4.tgz", - "integrity": "sha512-ltOGC9Jk0k8boe48Gk7SkJErwxt7MhwXtbNrBUyNCZcwcXSmGRdkKb2u0YO250PGvPsUtdqRjg7lVuIk1VtpCg==", - "requires": { - "@octokit/rest": "^18.0.0", - "scripting-tools": "^0.19.12" - } - }, - "gitignore-parser": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/gitignore-parser/-/gitignore-parser-0.0.2.tgz", - "integrity": "sha512-X6mpqUv59uWLGD4n3hZ8Cu8KbF2PMWPSFYmxZjdkpm3yOU7hSUYnzTkZI1mcWqchphvqyuz3/BhgBR4E/JtkCg==" - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "has-own-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", - "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", - "requires": { - "@apidevtools/json-schema-ref-parser": "9.0.9" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "minimal-polyfills": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/minimal-polyfills/-/minimal-polyfills-2.2.2.tgz", - "integrity": "sha512-eEOUq/LH/DbLWihrxUP050Wi7H/N/I2dQT98Ep6SqOpmIbk4sXOI4wqalve66QoZa+6oljbZWU6I6T4dehQGmw==" - }, - "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" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "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" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "openapi-typescript-codegen": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.23.0.tgz", - "integrity": "sha512-gOJXy5g3H3HlLpVNN+USrNK2i2KYBmDczk9Xk34u6JorwrGiDJZUj+al4S+i9TXdfUQ/ZaLxE59Xf3wqkxGfqA==", - "requires": { - "camelcase": "^6.3.0", - "commander": "^9.3.0", - "fs-extra": "^10.1.0", - "handlebars": "^4.7.7", - "json-schema-ref-parser": "^9.0.9" - }, - "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==" - } - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-depth": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-depth/-/path-depth-1.0.0.tgz", - "integrity": "sha512-dEiwdXAQyLvOi6ktLqhFhjVelJiVsdp2xBX3BaUtYCCkMRZTwUiq7cha+A0myvAVXRHbXfjhfTf4mNoAWzm2iA==" - }, - "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==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "run-exclusive": { - "version": "2.2.17", - "resolved": "https://registry.npmjs.org/run-exclusive/-/run-exclusive-2.2.17.tgz", - "integrity": "sha512-RM/mJJuX0VVfkiCa/yWJLs8Tb2OTweRaKEbWtVj77jXz94ztheIFnfLtK91u7XY5iy4/tyVIXEdv+kQmC1E51w==", - "requires": { - "minimal-polyfills": "^2.2.1" - } - }, - "scripting-tools": { - "version": "0.19.14", - "resolved": "https://registry.npmjs.org/scripting-tools/-/scripting-tools-0.19.14.tgz", - "integrity": "sha512-KGRES70dEmcaCdpx3R88bLWmfA4mQ/EGikCQy0FGTZwx3y9F5yYkzEhwp02+ZTgpvF25JcNOhDBbOqL6z92kwg==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "tsafe": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tsafe/-/tsafe-1.1.3.tgz", - "integrity": "sha512-QQQoed5Acb7Qe/sjMwE/qg57mxD5MXqY5HcGN4i3QyhJiplpo79ABEZiOBvL1cRTOYIzeryWvWQ3xsotTA49MQ==" - }, - "uglify-js": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.3.tgz", - "integrity": "sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg==", - "optional": true - }, - "universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" - }, - "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==" - }, - "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" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==" - } - } -} diff --git a/deno-client/package.json b/deno-client/package.json deleted file mode 100644 index 581299bea8..0000000000 --- a/deno-client/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "denoify": { - "index": "index.ts" - }, - "dependencies": { - "denoify": "^1.3.1", - "openapi-typescript-codegen": "^0.23.0" - }, - "overrides": { - "evt": "2.4.6" - } -} diff --git a/deno-client/tsconfig.json b/deno-client/tsconfig.json deleted file mode 100644 index 6aee05753b..0000000000 --- a/deno-client/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "ES6", - "lib": ["es7", "es6", "dom"], - "declaration": true, - "outDir": "dist", - "strict": true, - "esModuleInterop": true - }, - "exclude": [ - "node_modules", - "dist" - ] - } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fb46dd7fb2..df2a99b38d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -159,6 +159,7 @@ services: # Configure the mounted Caddyfile and the exposed ports or use another reverse proxy if needed volumes: - ./Caddyfile:/etc/caddy/Caddyfile + - caddy_data:/data # - ./certs:/certs # Provide custom certificate files like cert.pem and key.pem to enable HTTPS - See the corresponding section in the Caddyfile ports: # To change the exposed port, simply change 80:80 to :80. No other changes needed @@ -176,3 +177,4 @@ volumes: worker_logs: null windmill_index: null lsp_cache: null + caddy_data: null diff --git a/docker/DockerfileFullEe b/docker/DockerfileFullEe index 9b2e1a9cf0..512dfd61bc 100644 --- a/docker/DockerfileFullEe +++ b/docker/DockerfileFullEe @@ -1,3 +1,22 @@ +FROM alpine:3.14 AS oracledb-client + +ARG TARGETPLATFORM + +# Oracle DB Client for amd64 +COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/amd64/lib + +# Oracle DB Client for arm64 +RUN mkdir -p /opt/oracle/23/arm64 \ + && cd /opt/oracle/23/arm64 \ + && wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linux-arm64.zip \ + && unzip instantclient-basiclite-linux-arm64.zip && rm instantclient-basiclite-linux-arm64.zip && mv instantclient* ./lib + +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + mv /opt/oracle/23/arm64/lib /opt/oracle/23/lib; \ + else \ + mv /opt/oracle/23/amd64/lib /opt/oracle/23/lib; \ + fi + FROM ghcr.io/windmill-labs/windmill-ee:dev COPY --from=rust:1.81.0 /usr/local/cargo /usr/local/cargo @@ -5,11 +24,12 @@ COPY --from=rust:1.81.0 /usr/local/rustup /usr/local/rustup RUN pip3 install ansible +# dotnet SDK COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet ENV DOTNET_ROOT="/opt/dotnet-sdk/bin" # Oracle DB Client -COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/lib +COPY --from=oracledb-client /opt/oracle/23/lib /opt/oracle/23/lib RUN apt-get -y update && apt-get install -y libaio1 RUN echo /opt/oracle/23/lib > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig diff --git a/docker/DockerfileNsjail b/docker/DockerfileNsjail index 975092006b..76511f4c53 100644 --- a/docker/DockerfileNsjail +++ b/docker/DockerfileNsjail @@ -21,6 +21,25 @@ RUN apt-get -y update \ RUN git clone -b master --single-branch https://github.com/google/nsjail.git . && git checkout dccf911fd2659e7b08ce9507c25b2b38ec2c5800 RUN make +FROM alpine:3.14 AS oracledb-client + +ARG TARGETPLATFORM + +# Oracle DB Client for amd64 +COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/amd64/lib + +# Oracle DB Client for arm64 +RUN mkdir -p /opt/oracle/23/arm64 \ + && cd /opt/oracle/23/arm64 \ + && wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linux-arm64.zip \ + && unzip instantclient-basiclite-linux-arm64.zip && rm instantclient-basiclite-linux-arm64.zip && mv instantclient* ./lib + +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + mv /opt/oracle/23/arm64/lib /opt/oracle/23/lib; \ + else \ + mv /opt/oracle/23/amd64/lib /opt/oracle/23/lib; \ + fi + FROM ghcr.io/windmill-labs/windmill-ee:dev RUN apt-get update && apt-get install -y libprotobuf-dev libnl-route-3-dev @@ -30,12 +49,13 @@ COPY --from=rust:1.80.1 /usr/local/rustup /usr/local/rustup RUN pip3 install ansible +# dotnet SDK COPY --from=bitnami/dotnet-sdk:9.0.101-debian-12-r0 /opt/bitnami/dotnet-sdk /opt/dotnet-sdk RUN ln -s /opt/dotnet-sdk/bin/dotnet /usr/bin/dotnet ENV DOTNET_ROOT="/opt/dotnet-sdk/bin" # Oracle DB Client -COPY --from=ghcr.io/oracle/oraclelinux9-instantclient:23 /usr/lib/oracle/23/client64/lib /opt/oracle/23/lib +COPY --from=oracledb-client /opt/oracle/23/lib /opt/oracle/23/lib RUN apt-get -y update && apt-get install -y libaio1 RUN echo /opt/oracle/23/lib > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig diff --git a/flake.nix b/flake.nix index 3678ed1485..219a103cd9 100644 --- a/flake.nix +++ b/flake.nix @@ -10,6 +10,7 @@ let pkgs = import nixpkgs { inherit system; + config.allowUnfree = true; overlays = [ (import rust-overlay) ]; }; rust = pkgs.rust-bin.stable.latest.default.override { @@ -49,7 +50,7 @@ devShell = pkgs.mkShell { buildInputs = buildInputs ++ (with pkgs; [ git xcaddy sqlx-cli flock sccache - deno python3 python3Packages.pip go bun uv + deno python3 python3Packages.pip go bun uv dotnet-sdk_9 oracle-instantclient ]); packages = [ (pkgs.writeScriptBin "wm-caddy" '' @@ -102,12 +103,15 @@ UV_PATH = "${pkgs.uv}/bin/uv"; FLOCK_PATH = "${pkgs.flock}/bin/flock"; CARGO_PATH = "${rust}/bin/cargo"; + DOTNET_PATH = "${pkgs.dotnet-sdk_9}/bin/dotnet"; + DOTNET_ROOT = "${pkgs.dotnet-sdk_9}/share/dotnet"; + ORACLE_LIB_DIR = "${pkgs.oracle-instantclient.lib}/lib"; }; packages.default = self.packages.${system}.windmill; packages.windmill-client = pkgs.buildNpmPackage { name = "windmill-client"; version = (pkgs.lib.strings.trim (builtins.readFile ./version.txt)); - + src = pkgs.nix-gitignore.gitignoreSource [] ./frontend; nativeBuildInputs = with pkgs; [ pkg-config ]; buildInputs = with pkgs; [ nodejs pixman cairo pango ]; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 01d0951aca..b6f4baa4d8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.448.1", + "version": "1.454.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.448.1", + "version": "1.454.1", "license": "AGPL-3.0", "dependencies": { "@anthropic-ai/sdk": "^0.32.1", diff --git a/frontend/package.json b/frontend/package.json index bd4f81e8a5..747141abaa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.448.1", + "version": "1.454.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/ApiConnectForm.svelte b/frontend/src/lib/components/ApiConnectForm.svelte index 33c3ffb5e6..0e90ec56c7 100644 --- a/frontend/src/lib/components/ApiConnectForm.svelte +++ b/frontend/src/lib/components/ApiConnectForm.svelte @@ -4,12 +4,13 @@ import { base } from '$lib/base' import { emptySchema, emptyString } from '$lib/utils' import SchemaForm from './SchemaForm.svelte' - import SimpleEditor from './SimpleEditor.svelte' + import type SimpleEditor from './SimpleEditor.svelte' import Toggle from './Toggle.svelte' import TestConnection from './TestConnection.svelte' import SupabaseIcon from './icons/SupabaseIcon.svelte' import Popup from './common/popup/Popup.svelte' import Button from './common/button/Button.svelte' + import { Loader2 } from 'lucide-svelte' export let resourceType: string export let resourceTypeInfo: ResourceType | undefined @@ -195,13 +196,17 @@ >{error}{:else}
{/if}
- + {#await import('$lib/components/SimpleEditor.svelte')} + + {:then Module} + + {/await}
{:else if resourceTypeInfo?.format_extension}
@@ -209,13 +214,17 @@
- + {#await import('$lib/components/SimpleEditor.svelte')} + + {:then Module} + + {/await}
{:else} | undefined = undefined + export let appPath: string | undefined = undefined + export let computeS3ForceViewerPolicies: + | (() => + | { + allowed_resources: string[] + allow_user_resources: boolean + allow_workspace_resource: boolean + file_key_regex: string + } + | undefined) + | undefined = undefined + export let workspace: string | undefined = undefined $: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding) @@ -354,8 +368,9 @@
{#if diffStatus && typeof diffStatus === 'object' && diffStatus.diff !== 'same'}