Compare commits
22 Commits
v1.467.1
...
rf/benchTe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7882e355eb | ||
|
|
0b6d130efd | ||
|
|
31a8f24734 | ||
|
|
34587fea42 | ||
|
|
0577f999c2 | ||
|
|
25a49b74f9 | ||
|
|
1df9457bb1 | ||
|
|
fd1cfc0df5 | ||
|
|
f1c5b77d7a | ||
|
|
089ba7ae00 | ||
|
|
66451d4da1 | ||
|
|
1a54e1ed45 | ||
|
|
692de3e628 | ||
|
|
f90841ac5a | ||
|
|
c81a13cdac | ||
|
|
05659816e7 | ||
|
|
7c0152c353 | ||
|
|
b8e6d0da79 | ||
|
|
20547493e0 | ||
|
|
c6dbd239b4 | ||
|
|
b102ff4a46 | ||
|
|
4e477d1f58 |
2
.github/DockerfileBackendTests
vendored
2
.github/DockerfileBackendTests
vendored
@@ -40,7 +40,7 @@ RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VER
|
||||
|
||||
RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
|
||||
COPY --from=oven/bun:1.1.31 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.2.3 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
|
||||
319
.github/workflows/benchmark.yml
vendored
319
.github/workflows/benchmark.yml
vendored
@@ -8,317 +8,22 @@ on:
|
||||
jobs:
|
||||
benchmark_single:
|
||||
runs-on: ubicloud-standard-8
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_DB: windmill
|
||||
POSTGRES_PASSWORD: changeme
|
||||
POSTGRES_INITDB_ARGS: "-c shared_buffers=2GB -c work_mem=32MB -c effective_cache_size=4GB"
|
||||
options: >-
|
||||
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5
|
||||
--shm-size=2g
|
||||
|
||||
|
||||
windmill:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5 --health-cmd "curl
|
||||
http://localhost:8000/api/version"
|
||||
ports:
|
||||
- 8000:8000
|
||||
steps:
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: benchmark
|
||||
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
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json
|
||||
- name: Save benchmark results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark_single
|
||||
path: |
|
||||
*.json
|
||||
|
||||
benchmark_dedicated:
|
||||
runs-on: ubicloud-standard-8
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_DB: windmill
|
||||
POSTGRES_PASSWORD: changeme
|
||||
POSTGRES_INITDB_ARGS: "-c shared_buffers=2GB -c work_mem=32MB -c effective_cache_size=4GB"
|
||||
options: >-
|
||||
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5
|
||||
windmill:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
WORKER_GROUP: dedicated
|
||||
DEDICATED_WORKER: "admins:f/benchmarks/dedicated"
|
||||
options: >-
|
||||
--pull always --restart unless-stopped --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5 --health-cmd "curl
|
||||
http://localhost:8000/api/version"
|
||||
ports:
|
||||
- 8000:8000
|
||||
steps:
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: benchmark
|
||||
timeout-minutes: 20
|
||||
run: deno run --unstable -A -r
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts
|
||||
--no-warm-up -c
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_dedicated.json
|
||||
- name: Save benchmark results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark_dedicated
|
||||
path: |
|
||||
*.json
|
||||
|
||||
benchmark_4workers:
|
||||
runs-on: ubicloud-standard-8
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_DB: windmill
|
||||
POSTGRES_PASSWORD: changeme
|
||||
POSTGRES_INITDB_ARGS: "-c shared_buffers=2GB -c work_mem=32MB -c effective_cache_size=4GB"
|
||||
options: >-
|
||||
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5
|
||||
windmill:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5 --health-cmd "curl
|
||||
http://localhost:8000/api/version"
|
||||
ports:
|
||||
- 8000:8000
|
||||
windmill_1:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_2:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_3:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
steps:
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: benchmark
|
||||
timeout-minutes: 20
|
||||
run: deno run --unstable -A -r
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts
|
||||
-c
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json
|
||||
--workers 4
|
||||
--factor 3
|
||||
- name: Save benchmark results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark_4workers
|
||||
path: |
|
||||
*.json
|
||||
|
||||
benchmark_8workers:
|
||||
runs-on: ubicloud-standard-8
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_DB: windmill
|
||||
POSTGRES_PASSWORD: changeme
|
||||
POSTGRES_INITDB_ARGS: "-c shared_buffers=2GB -c work_mem=32MB -c effective_cache_size=4GB"
|
||||
options: >-
|
||||
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5
|
||||
windmill:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always --health-interval 10s --health-timeout 5s
|
||||
--health-retries 5 --health-cmd "curl
|
||||
http://localhost:8000/api/version"
|
||||
ports:
|
||||
- 8000:8000
|
||||
windmill_1:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_2:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_3:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_4:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_5:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_6:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
|
||||
windmill_7:
|
||||
image: ghcr.io/windmill-labs/windmill-ee:main
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
|
||||
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
|
||||
MODE: worker
|
||||
WORKER_GROUP: main
|
||||
WORKER_TAGS: deno,bun,go,python3,bash,dependency,flow,nativets
|
||||
options: >-
|
||||
--pull always
|
||||
steps:
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: benchmark
|
||||
timeout-minutes: 20
|
||||
run: deno run --unstable -A -r
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts
|
||||
-c
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json
|
||||
--workers 8
|
||||
--factor 3
|
||||
- name: Save benchmark results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark_8workers
|
||||
path: |
|
||||
*.json
|
||||
|
||||
benchmark_graphs:
|
||||
runs-on: ubicloud
|
||||
needs:
|
||||
- benchmark_single
|
||||
- benchmark_dedicated
|
||||
- benchmark_4workers
|
||||
- benchmark_8workers
|
||||
steps:
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: benchmarks
|
||||
- name: Download benchmark results
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
merge-multiple: true
|
||||
- name: graphs
|
||||
run: deno run --unstable -A -r
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_graphs.ts
|
||||
-c
|
||||
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/graphs_config.json
|
||||
- name: Push changes
|
||||
run: |
|
||||
ls -la
|
||||
pwd
|
||||
git add .
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git commit -m "Update benchmarks"
|
||||
git push
|
||||
docker run -d --network=host -e POSTGRES_PASSWORD=changeme -e POSTGRES_USER=postgres -e POSTGRES_DB=windmill -e POSTGRES_INITDB_ARGS="-c log_duration=on -c log_statement=all -c log_min_duration_statement=0 -c shared_buffers=2GB -c work_mem=32MB -c effective_cache_size=4GB -c shared_preload_libraries=auto_explain -c auto_explain.log_min_duration=5 -c auto_explain.log_analyze=on -c auto_explain.log_timing=on -c auto_explain.log_buffers=on -c auto_explain.log_verbose=on \
|
||||
-c log_statement=all \
|
||||
-c log_min_duration_statement=0 \
|
||||
-c shared_buffers=2GB \
|
||||
-c work_mem=32MB \
|
||||
-c effective_cache_size=4GB" \
|
||||
postgres
|
||||
sleep 5
|
||||
|
||||
docker run -d -it --network=host -e DATABASE_URL=postgres://postgres:changeme@localhost/windmill ghcr.io/windmill-labs/windmill:main
|
||||
sleep 10
|
||||
deno run --unstable -A -r https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts -c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json
|
||||
@@ -189,9 +189,9 @@ ENV TZ=Etc/UTC
|
||||
COPY --from=builder /frontend/build /static_frontend
|
||||
COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
|
||||
|
||||
COPY --from=denoland/deno:2.1.2 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
COPY --from=denoland/deno:2.2.1 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
COPY --from=oven/bun:1.1.43 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.2.3 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
|
||||
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
@@ -130,28 +130,28 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "teams_command_script",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 26,
|
||||
"name": "teams_team_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 27,
|
||||
"name": "teams_team_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -185,10 +185,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow SET path = $1, summary = $2, description = $3,dependency_job = NULL, draft_only = NULL, tag = $4, dedicated_worker = $5, visible_to_runner_only = $6, on_behalf_of_email = $7, value = $8, schema = $9::text::json, edited_by = $10, edited_at = now()\n WHERE path = $11 AND workspace_id = $12",
|
||||
"query": "UPDATE flow SET path = $1, summary = $2, description = $3,dependency_job = NULL, lock_error_logs = '', draft_only = NULL, tag = $4, dedicated_worker = $5, visible_to_runner_only = $6, on_behalf_of_email = $7, value = $8, schema = $9::text::json, edited_by = $10, edited_at = now()\n WHERE path = $11 AND workspace_id = $12",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -21,5 +21,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ba8bde5018fdf7b12f85cd3a6557c4accfc78bf160c1277f35d9d8ddcd056963"
|
||||
"hash": "2b9607ed838c8c62eb0f2856420389f7be648f52edbb875ff52c96219ed3ba84"
|
||||
}
|
||||
@@ -130,28 +130,28 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "teams_command_script",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 26,
|
||||
"name": "teams_team_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 27,
|
||||
"name": "teams_team_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "ai_models",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "code_completion_model",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -185,10 +185,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
23
backend/.sqlx/query-97bf27f210572499b42ce04f19f116cc87ed06c49dcca04360250ddfd89d7ab3.json
generated
Normal file
23
backend/.sqlx/query-97bf27f210572499b42ce04f19f116cc87ed06c49dcca04360250ddfd89d7ab3.json
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT lock_error_logs FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "lock_error_logs",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "97bf27f210572499b42ce04f19f116cc87ed06c49dcca04360250ddfd89d7ab3"
|
||||
}
|
||||
16
backend/.sqlx/query-ba285edd1c1b1e400e85168ff4f05cf5281fd341096d433c7c0e5712e7726fb0.json
generated
Normal file
16
backend/.sqlx/query-ba285edd1c1b1e400e85168ff4f05cf5281fd341096d433c7c0e5712e7726fb0.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow SET lock_error_logs = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ba285edd1c1b1e400e85168ff4f05cf5281fd341096d433c7c0e5712e7726fb0"
|
||||
}
|
||||
15
backend/.sqlx/query-d1876c46c0b1aba168efaebd3a056e999c400998eb699d862d718e7ab4c1f427.json
generated
Normal file
15
backend/.sqlx/query-d1876c46c0b1aba168efaebd3a056e999c400998eb699d862d718e7ab4c1f427.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow SET lock_error_logs = NULL WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d1876c46c0b1aba168efaebd3a056e999c400998eb699d862d718e7ab4c1f427"
|
||||
}
|
||||
14
backend/.sqlx/query-d8186f0cee285aa50db7626409aec7e0504b068ffa8bb185d9384ce1422fd3d1.json
generated
Normal file
14
backend/.sqlx/query-d8186f0cee285aa50db7626409aec7e0504b068ffa8bb185d9384ce1422fd3d1.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM audit WHERE timestamp <= now() - ($1::bigint::text || ' s')::interval",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d8186f0cee285aa50db7626409aec7e0504b068ffa8bb185d9384ce1422fd3d1"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO flow (workspace_id, path, summary, description, dependency_job, draft_only, tag, dedicated_worker, visible_to_runner_only, on_behalf_of_email, value, schema, edited_by, edited_at) \n VALUES ($1, $2, $3, $4, NULL, $5, $6, $7, $8, $9, $10, $11::text::json, $12, now())",
|
||||
"query": "INSERT INTO flow (workspace_id, path, summary, description, dependency_job, lock_error_logs, draft_only, tag, dedicated_worker, visible_to_runner_only, on_behalf_of_email, value, schema, edited_by, edited_at) \n VALUES ($1, $2, $3, $4, NULL, '', $5, $6, $7, $8, $9, $10, $11::text::json, $12, now())",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -21,5 +21,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4205d237c123d8d1c9ff2d61118027a80ccc8cd75e9703cb1d014b45f57c2be6"
|
||||
"hash": "e4f1ee1568ce3c186b569421c6c8a3039f73d04fc53c67c70e67371f06416ef3"
|
||||
}
|
||||
@@ -41,11 +41,11 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
3156
backend/Cargo.lock
generated
3156
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -160,7 +160,6 @@ 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"
|
||||
@@ -212,22 +211,28 @@ itertools = "^0"
|
||||
regex = "^1"
|
||||
semver = "^1"
|
||||
|
||||
deno_fetch = "0.203.0"
|
||||
deno_tls = "0.166.0"
|
||||
deno_console = "0.179.0"
|
||||
deno_url = "0.179.0"
|
||||
deno_webidl = "0.179.0"
|
||||
deno_web = "0.210.0"
|
||||
deno_net = "0.171.0"
|
||||
deno_core = "0.321.0"
|
||||
deno_ast = { version = "=0.43.3", features = ["transpiling"] }
|
||||
deno_permissions = "0.39.0"
|
||||
v8 = "=134.4.0" # Exact version
|
||||
deno_fetch = "0.216.0"
|
||||
deno_tls = "0.179.0"
|
||||
deno_console = "0.192.0"
|
||||
deno_url = "0.192.0"
|
||||
deno_webidl = "0.192.0"
|
||||
deno_web = "0.223.0"
|
||||
deno_io = "0.102.0"
|
||||
deno_net = "0.184.0"
|
||||
deno_core = "0.338.0"
|
||||
deno_ast = { version = "=0.44.0", features = ["transpiling"] }
|
||||
deno_permissions = "0.51.0"
|
||||
deno_runtime = { version = "0.200.0", features = ["transpile"] }
|
||||
deno_telemetry = "0.14.0"
|
||||
deno_error = "=0.5.5"
|
||||
|
||||
swc_common = "=0.37.5"
|
||||
swc_ecma_parser = "=0.149.1"
|
||||
swc_ecma_ast = "=0.118.2"
|
||||
swc_ecma_visit = "=0.104.8"
|
||||
|
||||
|
||||
async-recursion = "^1"
|
||||
|
||||
base64 = "^0"
|
||||
@@ -264,9 +269,9 @@ once_cell = "1.17.1"
|
||||
gosyn = "0.2.6"
|
||||
bytes = "1.4.0"
|
||||
gethostname = "0.4.3"
|
||||
wasm-bindgen = "=0.2.92"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "^0"
|
||||
serde-wasm-bindgen = "^0"
|
||||
wasm-bindgen-test = "^0"
|
||||
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"]}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE flow DROP COLUMN lock_error_logs;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE flow ADD COLUMN lock_error_logs TEXT;
|
||||
@@ -731,6 +731,22 @@ pub async fn delete_expired_items(db: &DB) -> () {
|
||||
Err(e) => tracing::error!("Error deleting log file: {:?}", e),
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let audit_retention_secs = 1 * 60 * 60 * 24 * 14;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let audit_retention_secs = 1 * 60 * 60 * 24 * 365;
|
||||
|
||||
if let Err(e) = sqlx::query_scalar!(
|
||||
"DELETE FROM audit WHERE timestamp <= now() - ($1::bigint::text || ' s')::interval",
|
||||
audit_retention_secs,
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Error deleting audit log on CE: {:?}", e);
|
||||
}
|
||||
|
||||
let job_retention_secs = *JOB_RETENTION_SECS.read().await;
|
||||
if job_retention_secs > 0 {
|
||||
match db.begin().await {
|
||||
|
||||
@@ -2900,7 +2900,7 @@ async fn test_flow_lock_all(db: Pool<Postgres>) {
|
||||
.await
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.subtype_0
|
||||
.open_flow
|
||||
.value
|
||||
.modules;
|
||||
modules.into_iter()
|
||||
|
||||
@@ -5366,6 +5366,27 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Flow"
|
||||
|
||||
/w/{workspace}/flows/deployment_status/p/{path}:
|
||||
get:
|
||||
summary: get flow deployment status
|
||||
operationId: getFlowDeploymentStatus
|
||||
tags:
|
||||
- flow
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: flow status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
lock_error_logs:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/flows/get_triggers_count/{path}:
|
||||
get:
|
||||
summary: get triggers count of flow
|
||||
@@ -14336,6 +14357,10 @@ components:
|
||||
allOf:
|
||||
- $ref: "../../openflow.openapi.yaml#/components/schemas/OpenFlow"
|
||||
- $ref: "#/components/schemas/FlowMetadata"
|
||||
- type: object
|
||||
properties:
|
||||
lock_error_logs:
|
||||
type: string
|
||||
|
||||
ExtraPerms:
|
||||
type: object
|
||||
|
||||
@@ -422,7 +422,7 @@ impl TryFrom<&str> for AIProvider {
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AIResource {
|
||||
pub path: String,
|
||||
pub path: Option<String>,
|
||||
pub provider: AIProvider,
|
||||
}
|
||||
|
||||
@@ -485,22 +485,26 @@ async fn proxy(
|
||||
let ai_resource = serde_json::from_value::<AIResource>(ai_resource.unwrap())
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
|
||||
let path = ai_resource.path.unwrap_or("".to_string());
|
||||
if path.is_empty() {
|
||||
return Err(Error::BadRequest("Resource path is empty".to_string()));
|
||||
}
|
||||
let resource = sqlx::query_scalar!(
|
||||
"SELECT value
|
||||
FROM resource
|
||||
WHERE path = $1 AND workspace_id = $2",
|
||||
&ai_resource.path,
|
||||
&path,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::NotFound(format!(
|
||||
"Could not find the {:?} resource at path {}, update the resource path in the workspace settings", ai_resource.provider, ai_resource.path
|
||||
"Could not find the {:?} resource at path {}, update the resource path in the workspace settings", ai_resource.provider, path
|
||||
))
|
||||
})?;
|
||||
|
||||
(resource, ai_resource.path, ai_resource.provider)
|
||||
(resource, path, ai_resource.provider)
|
||||
};
|
||||
|
||||
if resource.is_none() {
|
||||
|
||||
@@ -59,6 +59,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get_triggers_count/*path", get(get_triggers_count))
|
||||
.route("/list_tokens/*path", get(list_tokens))
|
||||
.route("/get/*path", get(get_flow_by_path))
|
||||
.route("/deployment_status/p/*path", get(get_deployment_status))
|
||||
.route("/get/draft/*path", get(get_flow_by_path_w_draft))
|
||||
.route("/exists/*path", get(exists_flow_by_path))
|
||||
.route("/list_paths", get(list_paths))
|
||||
@@ -356,8 +357,8 @@ async fn create_flow(
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO flow (workspace_id, path, summary, description, \
|
||||
dependency_job, draft_only, tag, dedicated_worker, visible_to_runner_only, on_behalf_of_email, value, schema, edited_by, edited_at)
|
||||
VALUES ($1, $2, $3, $4, NULL, $5, $6, $7, $8, $9, $10, $11::text::json, $12, now())",
|
||||
dependency_job, lock_error_logs, draft_only, tag, dedicated_worker, visible_to_runner_only, on_behalf_of_email, value, schema, edited_by, edited_at)
|
||||
VALUES ($1, $2, $3, $4, NULL, '', $5, $6, $7, $8, $9, $10, $11::text::json, $12, now())",
|
||||
w_id,
|
||||
nf.path,
|
||||
nf.summary,
|
||||
@@ -683,7 +684,7 @@ async fn update_flow(
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE flow SET path = $1, summary = $2, description = $3,\
|
||||
dependency_job = NULL, draft_only = NULL, tag = $4, dedicated_worker = $5, visible_to_runner_only = $6, on_behalf_of_email = $7, \
|
||||
dependency_job = NULL, lock_error_logs = '', draft_only = NULL, tag = $4, dedicated_worker = $5, visible_to_runner_only = $6, on_behalf_of_email = $7, \
|
||||
value = $8, schema = $9::text::json, edited_by = $10, edited_at = now()
|
||||
WHERE path = $11 AND workspace_id = $12",
|
||||
if is_new_path { flow_path } else { &nf.path }, // if new path, do not rename directly (to avoid flow_version foreign key constraint)
|
||||
@@ -950,6 +951,31 @@ async fn list_tokens(
|
||||
list_tokens_internal(&db, &w_id, &path, true).await
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize)]
|
||||
struct DeploymentStatus {
|
||||
lock_error_logs: Option<String>,
|
||||
}
|
||||
async fn get_deployment_status(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> JsonResult<DeploymentStatus> {
|
||||
let path = path.to_path();
|
||||
let mut tx = db.begin().await?;
|
||||
let status_o: Option<DeploymentStatus> = sqlx::query_as!(
|
||||
DeploymentStatus,
|
||||
"SELECT lock_error_logs FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
path,
|
||||
w_id,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let status = not_found_if_none(status_o, "DeploymentStatus", path)?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(status))
|
||||
}
|
||||
|
||||
async fn get_flow_by_path(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -961,7 +987,7 @@ async fn get_flow_by_path(
|
||||
|
||||
let flow_o = if query.with_starred_info.unwrap_or(false) {
|
||||
sqlx::query_as::<_, FlowWithStarred>(
|
||||
"SELECT flow.workspace_id, flow.path, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by, favorite.path IS NOT NULL as starred
|
||||
"SELECT flow.workspace_id, flow.path, flow.lock_error_logs, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by, favorite.path IS NOT NULL as starred
|
||||
FROM flow
|
||||
LEFT JOIN favorite
|
||||
ON favorite.favorite_kind = 'flow'
|
||||
@@ -978,7 +1004,7 @@ async fn get_flow_by_path(
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as::<_, FlowWithStarred>(
|
||||
"SELECT flow.workspace_id, flow.path, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by, NULL as starred
|
||||
"SELECT flow.workspace_id, flow.path, flow.lock_error_logs, flow.summary, flow.description, flow.archived, flow.extra_perms, flow.draft_only, flow.dedicated_worker, flow.tag, flow.ws_error_handler_muted, flow.timeout, flow.visible_to_runner_only, flow.on_behalf_of_email, flow_version.schema, flow_version.value, flow_version.created_at as edited_at, flow_version.created_by as edited_by, NULL as starred
|
||||
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"
|
||||
|
||||
@@ -732,7 +732,8 @@ async fn edit_copilot_config(
|
||||
.await?;
|
||||
|
||||
if let Some(cached) = AI_KEY_CACHE.get(&w_id) {
|
||||
if cached.path != parsed_ai_resource.path {
|
||||
if parsed_ai_resource.path.is_none() || parsed_ai_resource.path.unwrap() != cached.path
|
||||
{
|
||||
AI_KEY_CACHE.remove(&w_id);
|
||||
}
|
||||
}
|
||||
@@ -1362,7 +1363,7 @@ struct UsedTriggers {
|
||||
pub kafka_used: bool,
|
||||
pub nats_used: bool,
|
||||
pub postgres_used: bool,
|
||||
pub sqs_used: bool
|
||||
pub sqs_used: bool,
|
||||
}
|
||||
|
||||
async fn get_used_triggers(
|
||||
|
||||
@@ -60,6 +60,8 @@ pub struct FlowWithStarred {
|
||||
pub flow: Flow,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub starred: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lock_error_logs: Option<String>,
|
||||
}
|
||||
|
||||
fn is_none_or_false(b: &Option<bool>) -> bool {
|
||||
|
||||
@@ -20,7 +20,7 @@ flow_testing = []
|
||||
cloud = []
|
||||
sqlx = []
|
||||
deno_core = ["dep:deno_fetch", "dep:deno_webidl", "dep:deno_web", "dep:deno_net", "dep:deno_console", "dep:deno_url", "dep:deno_core",
|
||||
"dep:deno_ast", "dep:deno_tls", "dep:deno_permissions"]
|
||||
"dep:deno_ast", "dep:deno_tls", "dep:deno_permissions", "dep:deno_io", "dep:deno_runtime", "dep:deno_telemetry", "dep:deno_error"]
|
||||
otel = ["windmill-common/otel", "dep:opentelemetry"]
|
||||
dind = ["dep:bollard"]
|
||||
php = ["dep:windmill-parser-php"]
|
||||
@@ -70,6 +70,7 @@ dyn-iter.workspace = true
|
||||
once_cell.workspace = true
|
||||
tokio-postgres.workspace = true
|
||||
bit-vec.workspace = true
|
||||
deno_telemetry = { workspace = true, optional = true }
|
||||
deno_fetch = { workspace = true, optional = true }
|
||||
deno_webidl = { workspace = true, optional = true }
|
||||
deno_web = { workspace = true, optional = true }
|
||||
@@ -80,6 +81,8 @@ deno_core = { workspace = true, optional = true }
|
||||
deno_ast = { workspace = true, optional = true }
|
||||
deno_tls = { workspace = true, optional = true }
|
||||
deno_permissions = { workspace = true, optional = true }
|
||||
deno_io = { workspace = true, optional = true }
|
||||
deno_error = { workspace = true, optional = true }
|
||||
|
||||
postgres-native-tls.workspace = true
|
||||
native-tls.workspace = true
|
||||
@@ -118,3 +121,6 @@ deno_core = { workspace = true, optional = true }
|
||||
deno_ast = { workspace = true, optional = true }
|
||||
deno_tls = { workspace = true, optional = true }
|
||||
deno_permissions = { workspace = true, optional = true }
|
||||
deno_io = { workspace = true, optional = true }
|
||||
deno_runtime = { workspace = true, optional = true }
|
||||
deno_telemetry = { workspace = true, optional = true }
|
||||
@@ -32,9 +32,10 @@ impl FetchPermissions for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn check_read<'a>(
|
||||
&mut self,
|
||||
_resolved: bool,
|
||||
_p: &'a std::path::Path,
|
||||
_api_name: &str,
|
||||
) -> Result<Cow<'a, Path>, deno_permissions::PermissionCheckError> {
|
||||
) -> Result<Cow<'a, std::path::Path>, deno_io::fs::FsError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
}
|
||||
@@ -95,7 +96,7 @@ fn main() {
|
||||
println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());
|
||||
|
||||
let exts = vec![
|
||||
// deno_telemetry::deno_telemetry::init_ops_and_esm(),
|
||||
deno_telemetry::deno_telemetry::init_ops_and_esm(),
|
||||
deno_webidl::deno_webidl::init_ops_and_esm(),
|
||||
deno_url::deno_url::init_ops_and_esm(),
|
||||
deno_console::deno_console::init_ops_and_esm(),
|
||||
@@ -117,7 +118,9 @@ fn main() {
|
||||
deno_core::snapshot::CreateSnapshotOptions {
|
||||
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
|
||||
startup_snapshot: None,
|
||||
extension_transpiler: None,
|
||||
extension_transpiler: Some(std::rc::Rc::new(|specifier, source| {
|
||||
deno_runtime::transpile::maybe_transpile_source(specifier, source)
|
||||
})),
|
||||
extensions: exts,
|
||||
with_runtime_cb: None,
|
||||
skip_op_registration: false,
|
||||
|
||||
@@ -108,9 +108,10 @@ impl FetchPermissions for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn check_read<'a>(
|
||||
&mut self,
|
||||
_resolved: bool,
|
||||
p: &'a std::path::Path,
|
||||
_api_name: &str,
|
||||
) -> Result<Cow<'a, std::path::Path>, deno_permissions::PermissionCheckError> {
|
||||
) -> Result<Cow<'a, std::path::Path>, deno_io::fs::FsError> {
|
||||
Ok(Cow::Borrowed(p))
|
||||
}
|
||||
}
|
||||
@@ -554,12 +555,17 @@ function get_from_env(name) {{
|
||||
async fn op_variable(
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
#[string] path: String,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
) -> Result<String, deno_error::JsErrorBox> {
|
||||
let client = op_state.borrow().borrow::<OptAuthedClient>().0.clone();
|
||||
if let Some(client) = client {
|
||||
Ok(client.get_variable_value(&path).await?)
|
||||
Ok(client
|
||||
.get_variable_value(&path)
|
||||
.await
|
||||
.map_err(|e| deno_error::JsErrorBox::generic(e.to_string()))?)
|
||||
} else {
|
||||
anyhow::bail!("No client found in op state");
|
||||
Err(deno_error::JsErrorBox::generic(
|
||||
"No client found in op state",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,16 +575,18 @@ async fn op_variable(
|
||||
async fn op_get_result(
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
#[string] id: String,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
) -> Result<String, deno_error::JsErrorBox> {
|
||||
let client = op_state.borrow().borrow::<OptAuthedClient>().0.clone();
|
||||
if let Some(client) = client {
|
||||
let result = client
|
||||
client
|
||||
.get_completed_job_result::<Box<RawValue>>(&id, None)
|
||||
.await?
|
||||
.clone();
|
||||
Ok(result.get().to_string())
|
||||
.await
|
||||
.map_err(|e| deno_error::JsErrorBox::generic(e.to_string()))
|
||||
.map(|x| x.get().to_string())
|
||||
} else {
|
||||
anyhow::bail!("No client found in op state");
|
||||
Err(deno_error::JsErrorBox::generic(
|
||||
"No client found in op state",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,7 +597,7 @@ async fn op_get_id(
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
#[string] flow_job_id: String,
|
||||
#[string] node_id: String,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> Result<Option<String>, deno_error::JsErrorBox> {
|
||||
let client = op_state.borrow().borrow::<OptAuthedClient>().0.clone();
|
||||
if let Some(client) = client {
|
||||
let result = client
|
||||
@@ -602,7 +610,9 @@ async fn op_get_id(
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("No client found in op state");
|
||||
Err(deno_error::JsErrorBox::generic(
|
||||
"No client found in op state",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,15 +622,18 @@ async fn op_get_id(
|
||||
async fn op_resource(
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
#[string] path: String,
|
||||
) -> Result<Option<String>, anyhow::Error> {
|
||||
) -> Result<Option<String>, deno_error::JsErrorBox> {
|
||||
let client = op_state.borrow().borrow::<OptAuthedClient>().0.clone();
|
||||
if let Some(client) = client {
|
||||
client
|
||||
.get_resource_value_interpolated::<Option<Box<RawValue>>>(&path, None)
|
||||
.await
|
||||
.map(|x| x.map(|x| x.get().to_string()))
|
||||
.map_err(|e| deno_error::JsErrorBox::generic(e.to_string()))
|
||||
} else {
|
||||
anyhow::bail!("No client found in op state");
|
||||
Err(deno_error::JsErrorBox::generic(
|
||||
"No client found in op state",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,6 +835,7 @@ pub async fn eval_fetch_timeout(
|
||||
};
|
||||
|
||||
let exts: Vec<Extension> = vec![
|
||||
deno_telemetry::deno_telemetry::init_ops(),
|
||||
deno_webidl::deno_webidl::init_ops(),
|
||||
deno_url::deno_url::init_ops(),
|
||||
deno_console::deno_console::init_ops(),
|
||||
@@ -953,6 +967,9 @@ fn write_error_expr(expr: &str, uuid: &Uuid) {
|
||||
}
|
||||
};
|
||||
|
||||
if std::env::var("PRINT_NATIVE_ERRORS").is_ok() {
|
||||
tracing::info!("native error for job {uuid}: {expr}");
|
||||
}
|
||||
if dir_entries >= 100 {
|
||||
tracing::info!("Too many error files in {ERROR_DIR}, skipping write");
|
||||
return;
|
||||
|
||||
@@ -621,7 +621,8 @@ pub async fn handle_flow_dependency_job(
|
||||
tx = clear_dependency_parent_path(&parent_path, &job_path, &job.workspace_id, "flow", tx)
|
||||
.await?;
|
||||
let modified_ids;
|
||||
(flow.modules, tx, modified_ids) = lock_modules(
|
||||
let errors;
|
||||
(flow.modules, tx, modified_ids, errors) = lock_modules(
|
||||
flow.modules,
|
||||
job,
|
||||
mem_peak,
|
||||
@@ -638,6 +639,43 @@ pub async fn handle_flow_dependency_job(
|
||||
occupancy_metrics,
|
||||
)
|
||||
.await?;
|
||||
if !errors.is_empty() {
|
||||
let error_message = errors
|
||||
.iter()
|
||||
.map(|e| format!("{}: {}", e.id, e.error))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
let logs2 = sqlx::query_scalar!(
|
||||
"SELECT logs FROM job_logs WHERE job_id = $1 AND workspace_id = $2",
|
||||
&job.id,
|
||||
&job.workspace_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or_else(|| "no logs".to_string());
|
||||
sqlx::query!(
|
||||
"UPDATE flow SET lock_error_logs = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
&format!("{logs2}\n{error_message}"),
|
||||
&job.script_path(),
|
||||
&job.workspace_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
return Err(Error::ExecutionErr(format!(
|
||||
"Error locking flow modules:\n{}\n\nlogs:\n{}",
|
||||
error_message,
|
||||
remove_ansi_codes(&logs2)
|
||||
)));
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE flow SET lock_error_logs = NULL WHERE path = $1 AND workspace_id = $2",
|
||||
&job.script_path(),
|
||||
&job.workspace_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
}
|
||||
let new_flow_value = Json(serde_json::value::to_raw_value(&flow).map_err(to_anyhow)?);
|
||||
|
||||
// Re-check cancellation to ensure we don't accidentally override a flow.
|
||||
@@ -772,6 +810,7 @@ async fn lock_modules<'c>(
|
||||
Vec<FlowModule>,
|
||||
sqlx::Transaction<'c, sqlx::Postgres>,
|
||||
Vec<String>,
|
||||
Vec<LockModuleError>,
|
||||
)> {
|
||||
let mut new_flow_modules = Vec::new();
|
||||
let mut modified_ids = Vec::new();
|
||||
@@ -802,7 +841,7 @@ async fn lock_modules<'c>(
|
||||
parallelism,
|
||||
} => {
|
||||
let nmodules;
|
||||
(nmodules, tx, nmodified_ids) = Box::pin(lock_modules(
|
||||
(nmodules, tx, modified_ids, errors) = Box::pin(lock_modules(
|
||||
modules,
|
||||
job,
|
||||
mem_peak,
|
||||
@@ -835,7 +874,8 @@ async fn lock_modules<'c>(
|
||||
for mut b in branches {
|
||||
let nmodules;
|
||||
let inner_modified_ids;
|
||||
(nmodules, tx, inner_modified_ids) = Box::pin(lock_modules(
|
||||
let inner_errors;
|
||||
(nmodules, tx, inner_modified_ids, inner_errors) = Box::pin(lock_modules(
|
||||
b.modules,
|
||||
job,
|
||||
mem_peak,
|
||||
@@ -853,6 +893,7 @@ async fn lock_modules<'c>(
|
||||
))
|
||||
.await?;
|
||||
nmodified_ids.extend(inner_modified_ids);
|
||||
errors.extend(inner_errors);
|
||||
b.modules = nmodules;
|
||||
nbranches.push(b)
|
||||
}
|
||||
@@ -860,7 +901,7 @@ async fn lock_modules<'c>(
|
||||
}
|
||||
FlowModuleValue::WhileloopFlow { modules, modules_node, skip_failures } => {
|
||||
let nmodules;
|
||||
(nmodules, tx, nmodified_ids) = Box::pin(lock_modules(
|
||||
(nmodules, tx, nmodified_ids, errors) = Box::pin(lock_modules(
|
||||
modules,
|
||||
job,
|
||||
mem_peak,
|
||||
@@ -890,8 +931,8 @@ async fn lock_modules<'c>(
|
||||
for mut b in branches {
|
||||
let nmodules;
|
||||
let inner_modified_ids;
|
||||
|
||||
(nmodules, tx, inner_modified_ids) = Box::pin(lock_modules(
|
||||
let inner_errors;
|
||||
(nmodules, tx, inner_modified_ids, inner_errors) = Box::pin(lock_modules(
|
||||
b.modules,
|
||||
job,
|
||||
mem_peak,
|
||||
@@ -909,11 +950,13 @@ async fn lock_modules<'c>(
|
||||
))
|
||||
.await?;
|
||||
nmodified_ids.extend(inner_modified_ids);
|
||||
errors.extend(inner_errors);
|
||||
b.modules = nmodules;
|
||||
nbranches.push(b)
|
||||
}
|
||||
let ndefault;
|
||||
(ndefault, tx, nmodified_ids) = Box::pin(lock_modules(
|
||||
let ninner_errors;
|
||||
(ndefault, tx, nmodified_ids, ninner_errors) = Box::pin(lock_modules(
|
||||
default,
|
||||
job,
|
||||
mem_peak,
|
||||
@@ -930,6 +973,7 @@ async fn lock_modules<'c>(
|
||||
occupancy_metrics,
|
||||
))
|
||||
.await?;
|
||||
errors.extend(ninner_errors);
|
||||
e.value = FlowModuleValue::BranchOne {
|
||||
branches: nbranches,
|
||||
default: ndefault,
|
||||
@@ -1048,29 +1092,8 @@ async fn lock_modules<'c>(
|
||||
new_flow_modules.push(e);
|
||||
continue;
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
let error_message = errors
|
||||
.iter()
|
||||
.map(|e| format!("{}: {}", e.id, e.error))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
let logs2 = sqlx::query_scalar!(
|
||||
"SELECT logs FROM job_logs WHERE job_id = $1 AND workspace_id = $2",
|
||||
&job.id,
|
||||
&job.workspace_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or_else(|| "no logs".to_string());
|
||||
|
||||
return Err(Error::ExecutionErr(format!(
|
||||
"Error locking flow modules:\n{}\n\nlogs:\n{}",
|
||||
error_message,
|
||||
remove_ansi_codes(&logs2)
|
||||
)));
|
||||
}
|
||||
Ok((new_flow_modules, tx, modified_ids))
|
||||
Ok((new_flow_modules, tx, modified_ids, errors))
|
||||
}
|
||||
|
||||
async fn insert_flow_node<'c>(
|
||||
|
||||
43
benchmarks/bench.sh
Normal file
43
benchmarks/bench.sh
Normal file
@@ -0,0 +1,43 @@
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker
|
||||
|
||||
|
||||
mkdir pg_logs
|
||||
chmod 777 pg_logs
|
||||
|
||||
sudo docker run --network=host -e POSTGRES_PASSWORD=changeme -e POSTGRES_USER=postgres -e POSTGRES_DB=windmill -e POSTGRES_INITDB_ARGS="-c log_duration=on -c log_statement=all -c log_min_duration_statement=0 -c shared_buffers=2GB -c work_mem=32MB -c effective_cache_size=4GB -c shared_preload_libraries=auto_explain -c auto_explain.log_min_duration=5 -c auto_explain.log_analyze=on -c auto_explain.log_timing=on -c auto_explain.log_buffers=on -c auto_explain.log_verbose=on \
|
||||
-c log_statement=all \
|
||||
-c log_min_duration_statement=0 \
|
||||
-c shared_buffers=2GB \
|
||||
-c work_mem=32MB \
|
||||
-c effective_cache_size=4GB \
|
||||
-c shared_preload_libraries=auto_explain \
|
||||
-c auto_explain.log_min_duration=5 \
|
||||
-c auto_explain.log_analyze=on \
|
||||
-c auto_explain.log_timing=on \
|
||||
-c auto_explain.log_buffers=on \
|
||||
-c auto_explain.log_verbose=on \
|
||||
-c auto_explain.log_nested_statements=on \
|
||||
-c logging_collector=on \
|
||||
-c log_directory='/var/log/postgresql' \
|
||||
-c log_filename='postgresql.log'" \
|
||||
-v ~/pg_logs:/var/log/postgresql \
|
||||
postgres
|
||||
|
||||
|
||||
docker run -it --network=host -e DATABASE_URL=postgres://postgres:changeme@localhost/windmill ghcr.io/windmill-labs/windmill:main
|
||||
|
||||
|
||||
curl -fsSL https://deno.land/install.sh | sh
|
||||
|
||||
cat <<EOF > suite.json
|
||||
[
|
||||
{
|
||||
"kind": "noop",
|
||||
"jobs": 90000
|
||||
}
|
||||
]
|
||||
EOF
|
||||
|
||||
deno run --unstable -A -r https://raw.githubusercontent.com/windmill-labs/windmill/main/benchmarks/benchmark_suite.ts -c suite.json
|
||||
@@ -1,4 +0,0 @@
|
||||
FROM ghcr.io/windmill-labs/windmill-ee-nsjail:main
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y chromium
|
||||
@@ -20,7 +20,7 @@ RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
# Install UV
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.15/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
COPY --from=oven/bun:1.1.43 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.2.3 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
@@ -19,7 +19,7 @@ RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
# Install UV
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.5.15/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
COPY --from=oven/bun:1.1.43 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.2.3 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import SaveInputsButton from '$lib/components/SaveInputsButton.svelte'
|
||||
import { Button } from './common'
|
||||
import { ExternalLink } from 'lucide-svelte'
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let scriptHash: string | null = null
|
||||
@@ -89,6 +91,19 @@
|
||||
|
||||
<Pane class="px-4 py-4 h-full">
|
||||
<Section label="History" wrapperClass="h-full" small={true}>
|
||||
<svelte:fragment slot="action">
|
||||
<Button
|
||||
size="xs2"
|
||||
color="light"
|
||||
btnClasses="!text-tertiary"
|
||||
endIcon={{ icon: ExternalLink }}
|
||||
on:click={() => {
|
||||
window.open(`/runs/${runnableId}`, '_blank')
|
||||
}}
|
||||
>
|
||||
All runs
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<HistoricInputs
|
||||
bind:this={historicInputs}
|
||||
{runnableId}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
export let customCss: ComponentCustomCSS<'htmlcomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, worldStore, mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
@@ -38,7 +38,9 @@
|
||||
{#if render}
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
e?.preventDefault()
|
||||
if ($mode !== 'preview') {
|
||||
e?.preventDefault()
|
||||
}
|
||||
}}
|
||||
class="h-full w-full"
|
||||
>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
let goingTo: URL | undefined = undefined
|
||||
|
||||
beforeNavigate(async (newNavigationState) => {
|
||||
// console.log('beforeNavigate', newNavigationState, bypassBeforeNavigate)
|
||||
if (
|
||||
!bypassBeforeNavigate &&
|
||||
newNavigationState.to &&
|
||||
@@ -32,9 +31,13 @@
|
||||
) {
|
||||
// console.log('going to', newNavigationState.to.url)
|
||||
goingTo = newNavigationState.to.url
|
||||
newNavigationState.cancel()
|
||||
if (newNavigationState.type != 'popstate') {
|
||||
await tick() // make sure saved value is updated when clicking on save draft or deploy
|
||||
|
||||
async function openModal() {
|
||||
newNavigationState.cancel()
|
||||
if (newNavigationState.type != 'popstate') {
|
||||
await tick() // make sure saved value is updated when clicking on save draft or deploy
|
||||
}
|
||||
open = true
|
||||
}
|
||||
if (savedValue && modifiedValue) {
|
||||
const draftOrDeployed = cleanValueProperties({
|
||||
@@ -48,12 +51,11 @@
|
||||
) {
|
||||
bypassBeforeNavigate = true
|
||||
additionalExitAction?.()
|
||||
gotoUrl(goingTo)
|
||||
} else {
|
||||
open = true
|
||||
await openModal()
|
||||
}
|
||||
} else {
|
||||
open = true
|
||||
await openModal()
|
||||
}
|
||||
} else if (bypassBeforeNavigate) {
|
||||
bypassBeforeNavigate = false
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
Trash,
|
||||
ChevronUpSquare,
|
||||
Share,
|
||||
Badge,
|
||||
Loader2,
|
||||
GitFork,
|
||||
Play,
|
||||
@@ -67,18 +66,22 @@
|
||||
import PostgresTriggersPanel from '$lib/components/triggers/postgres/PostgresTriggersPanel.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import SqsTriggerPanel from '$lib/components/triggers/sqs/SqsTriggerPanel.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import LogViewer from '$lib/components/LogViewer.svelte'
|
||||
|
||||
let flow: Flow | undefined
|
||||
let can_write = false
|
||||
$: path = $page.params.path
|
||||
let shareModal: ShareModal
|
||||
let deploymentInProgress = false
|
||||
|
||||
let scheduledForStr: string | undefined = undefined
|
||||
let invisible_to_owner: boolean | undefined = undefined
|
||||
let overrideTag: string | undefined = undefined
|
||||
let inputSelected: 'saved' | 'history' | undefined = undefined
|
||||
let jsonView = false
|
||||
let deploymentInProgress = false
|
||||
|
||||
let intervalId: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
const triggersCount = writable<TriggersCount | undefined>(undefined)
|
||||
|
||||
@@ -129,12 +132,31 @@
|
||||
if (!flow.path.startsWith(`u/${$userStore?.username}`) && flow.path.split('/').length > 2) {
|
||||
invisible_to_owner = flow.visible_to_runner_only
|
||||
}
|
||||
intervalId && clearInterval(intervalId)
|
||||
deploymentInProgress = flow.lock_error_logs == ''
|
||||
if (deploymentInProgress) {
|
||||
intervalId = setInterval(syncer, 500)
|
||||
}
|
||||
can_write = canWrite(flow.path, flow.extra_perms!, $userStore)
|
||||
}
|
||||
|
||||
let isValid = true
|
||||
let loading = false
|
||||
|
||||
async function syncer(): Promise<void> {
|
||||
if (flow) {
|
||||
const status = await FlowService.getFlowDeploymentStatus({
|
||||
workspace: $workspaceStore!,
|
||||
path: flow.path
|
||||
})
|
||||
if (status.lock_error_logs == undefined || status.lock_error_logs != '') {
|
||||
deploymentInProgress = false
|
||||
flow.lock_error_logs = status.lock_error_logs
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function runFlow(
|
||||
scheduledForStr: string | undefined,
|
||||
args: Record<string, any>,
|
||||
@@ -198,7 +220,7 @@
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
label: `View runs`,
|
||||
label: `Runs`,
|
||||
buttonProps: {
|
||||
href: `${base}/runs/${flow.path}`,
|
||||
size: 'xs',
|
||||
@@ -328,6 +350,10 @@
|
||||
return menuItems
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
intervalId && clearInterval(intervalId)
|
||||
})
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
@@ -442,10 +468,17 @@
|
||||
</div>
|
||||
|
||||
{#if deploymentInProgress}
|
||||
<Badge color="yellow">
|
||||
<HeaderBadge color="yellow">
|
||||
<Loader2 size={12} class="inline animate-spin mr-1" />
|
||||
Deployment in progress
|
||||
</Badge>
|
||||
</HeaderBadge>
|
||||
{/if}
|
||||
{#if flow.lock_error_logs && flow.lock_error_logs != ''}
|
||||
<div class="bg-red-100 dark:bg-red-700 border-l-4 border-red-500 p-4" role="alert">
|
||||
<p class="font-bold">Error deploying this flow</p>
|
||||
<p> This flow has not been deployed successfully because of the following errors: </p>
|
||||
<LogViewer content={flow.lock_error_logs} isLoading={false} tag={undefined} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col align-left">
|
||||
@@ -587,7 +620,7 @@
|
||||
<PostgresTriggersPanel path={flow.path ?? ''} isFlow />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
<svelte:fragment slot="nats">
|
||||
<div class="p-2">
|
||||
<NatsTriggersPanel path={flow.path ?? ''} isFlow />
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
label: `View runs`,
|
||||
label: `Runs`,
|
||||
buttonProps: {
|
||||
href: `${base}/runs/${script.path}`,
|
||||
size: 'xs',
|
||||
|
||||
@@ -39,7 +39,7 @@ RUN /usr/local/go/bin/go install -v golang.org/x/tools/gopls@latest
|
||||
|
||||
RUN pip3 install tornado python-lsp-jsonrpc ruff-lsp
|
||||
|
||||
COPY --from=denoland/deno:2.1.2 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
COPY --from=denoland/deno:2.2.1 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
RUN mkdir -p /pyls/.cache
|
||||
|
||||
|
||||
Reference in New Issue
Block a user