From b9e3e053e4914e753bbb806e6b748c791edb92d2 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Fri, 27 Feb 2026 13:09:30 +0100 Subject: [PATCH 01/35] fix: prevent wm-cursor from hanging on stale cursor IPC sockets Co-Authored-By: Claude Opus 4.5 --- scripts/wm-cursor | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/scripts/wm-cursor b/scripts/wm-cursor index 596d89bd3c..d2c2300fb5 100755 --- a/scripts/wm-cursor +++ b/scripts/wm-cursor @@ -10,25 +10,30 @@ setopt err_exit no_unset pipe_fail local script_path=${0:A} -# --- Resolve Cursor remote CLI (most recently modified) --- +# --- Lazy Cursor CLI resolution (only when needed) --- -local -a cursor_bins=(~/.cursor-server/cli/servers/*/server/bin/remote-cli/cursor(NOm)) -if (( ${#cursor_bins} == 0 )); then - print -u2 "Error: Cursor remote CLI not found in ~/.cursor-server/cli/servers/" - exit 1 -fi -local cursor_bin=${cursor_bins[1]} +local cursor_bin= -# --- Refresh Cursor IPC socket (tmux may hold a stale one) --- -# Multiple stale sockets may exist; probe to find a live one +resolve_cursor_cli() { + [[ -n $cursor_bin ]] && return 0 -local sock -for sock in /tmp/vscode-ipc-*.sock(NOm); do - if VSCODE_IPC_HOOK_CLI=$sock $cursor_bin --status &>/dev/null; then - export VSCODE_IPC_HOOK_CLI=$sock - break + local -a cursor_bins=(~/.cursor-server/cli/servers/*/server/bin/remote-cli/cursor(NOm)) + if (( ${#cursor_bins} == 0 )); then + print -u2 "Error: Cursor remote CLI not found in ~/.cursor-server/cli/servers/" + exit 1 fi -done + cursor_bin=${cursor_bins[1]} + + # Refresh Cursor IPC socket (tmux may hold a stale one) + # Multiple stale sockets may exist; probe to find a live one + local sock + for sock in /tmp/vscode-ipc-*.sock(NOm); do + if timeout 2 env VSCODE_IPC_HOOK_CLI=$sock $cursor_bin --status &>/dev/null; then + export VSCODE_IPC_HOOK_CLI=$sock + break + fi + done +} # --- Helper functions --- @@ -133,6 +138,7 @@ parse_features_flag() { # --- Subcommands --- cmd_add() { + resolve_cursor_cli ensure_tmux check_dev_db @@ -166,6 +172,7 @@ cmd_open() { local name=${1:?Usage: wm-cursor open } shift + resolve_cursor_cli ensure_tmux check_dev_db @@ -202,6 +209,7 @@ cmd_close() { cmd_open_ee() { local name=${1:?Usage: wm-cursor open-ee } + resolve_cursor_cli local wt_path=$(workmux path $name) local main_repo_root="$(cd "$(git -C "$wt_path" rev-parse --git-common-dir 2>/dev/null)/.." && pwd)" From cdc0543747680267e30974037a2eb180a19062d9 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Fri, 27 Feb 2026 13:32:12 +0100 Subject: [PATCH 02/35] fix: remove review comments from discord notifications and support comment edits Co-Authored-By: Claude Opus 4.6 --- .github/workflows/discord-notification.yml | 22 +---------- .../shareable-discord-notification.yml | 37 +++++++++++++++++-- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/.github/workflows/discord-notification.yml b/.github/workflows/discord-notification.yml index 5eb48570e7..d731faafd9 100644 --- a/.github/workflows/discord-notification.yml +++ b/.github/workflows/discord-notification.yml @@ -9,9 +9,7 @@ on: issue_comment: types: - created - pull_request_review_comment: - types: - - created + - edited jobs: notify_discord_when_pr_opened: @@ -53,23 +51,7 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} COMMENT_AUTHOR: ${{ github.event.comment.user.login }} COMMENT_URL: ${{ github.event.comment.html_url }} - DISCORD_CHANNEL_ID: "1372204995868491786" - DISCORD_GUILD_ID: "930051556043276338" - secrets: - DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }} - - notify_discord_on_review_comment: - if: > - github.event_name == 'pull_request_review_comment' - && github.event.comment.user.login != 'cloudflare-workers-and-pages[bot]' - && github.event.comment.user.login != 'ellipsis-dev[bot]' - uses: ./.github/workflows/shareable-discord-notification.yml - with: - PR_STATUS: "comment" - PR_NUMBER: ${{ github.event.pull_request.number }} - COMMENT_BODY: ${{ github.event.comment.body }} - COMMENT_AUTHOR: ${{ github.event.comment.user.login }} - COMMENT_URL: ${{ github.event.comment.html_url }} + COMMENT_IS_EDIT: ${{ github.event.action == 'edited' }} DISCORD_CHANNEL_ID: "1372204995868491786" DISCORD_GUILD_ID: "930051556043276338" secrets: diff --git a/.github/workflows/shareable-discord-notification.yml b/.github/workflows/shareable-discord-notification.yml index cf8c7d9078..b0d3fed549 100644 --- a/.github/workflows/shareable-discord-notification.yml +++ b/.github/workflows/shareable-discord-notification.yml @@ -36,6 +36,10 @@ on: description: "The comment URL" type: string default: "" + COMMENT_IS_EDIT: + description: "Whether this is an edit of an existing comment" + type: string + default: "false" secrets: DISCORD_WEBHOOK_URL: description: "Discord Webhook URL" @@ -135,7 +139,7 @@ jobs: runs-on: ubuntu-latest if: ${{ inputs.PR_STATUS == 'comment' }} steps: - - name: Post comment to Discord thread + - name: Post or update comment in Discord thread env: BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }} @@ -144,6 +148,7 @@ jobs: COMMENT_BODY: ${{ inputs.COMMENT_BODY }} COMMENT_AUTHOR: ${{ inputs.COMMENT_AUTHOR }} COMMENT_URL: ${{ inputs.COMMENT_URL }} + COMMENT_IS_EDIT: ${{ inputs.COMMENT_IS_EDIT }} run: | # 1) Find the thread by PR number threads=$(curl -s -H "Authorization: Bot $BOT_TOKEN" \ @@ -172,10 +177,36 @@ jobs: truncated_body="$COMMENT_BODY" fi - # 3) Post the comment to the thread - message=$(printf '**%s** [commented](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body") + # 3) Build the message content + if [ "$COMMENT_IS_EDIT" = "true" ]; then + message=$(printf '**%s** [edited comment](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body") + else + message=$(printf '**%s** [commented](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body") + fi payload=$(jq -n --arg content "$message" '{content: $content, flags: 4, allowed_mentions: {parse: []}}') + # 4) If this is an edit, try to find and update the existing Discord message + if [ "$COMMENT_IS_EDIT" = "true" ]; then + # Search recent messages in the thread for one containing the comment URL + messages=$(curl -s -H "Authorization: Bot $BOT_TOKEN" \ + "https://discord.com/api/v10/channels/${thread_id}/messages?limit=100") + existing_msg_id=$(echo "$messages" | jq -r \ + --arg url "$COMMENT_URL" \ + '[.[] | select(.content | contains($url))] | first | .id // empty') + + if [ -n "$existing_msg_id" ]; then + echo "Updating existing Discord message $existing_msg_id" + curl -s -X PATCH \ + -H "Authorization: Bot $BOT_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$payload" \ + "https://discord.com/api/v10/channels/${thread_id}/messages/${existing_msg_id}" + exit 0 + fi + echo "Original Discord message not found, posting as new message" + fi + + # 5) Post a new message to the thread curl -s -X POST \ -H "Authorization: Bot $BOT_TOKEN" \ -H "Content-Type: application/json" \ From 7d9d16a6a3357981e5692023982ca1e670acfaae Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:59:14 +0100 Subject: [PATCH 03/35] feat: runScript inline for path and hash (#8019) * runScript inline for path and hash * Update backend/windmill-api/src/jobs.rs Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> * refactor: unify inline script param structs and deduplicate closures - Replace RunInlineScriptByPathFnParams and RunInlineScriptByHashFnParams with a single RunInlineScriptFnParams using InlineScriptTarget enum - Collapse two nearly-identical closures in worker.rs into one - Merge duplicate InlineByPath/InlineByHash into InlineScriptArgs - Extract shared run_inline_script_inner helper in API handler - Add missing check_scopes to run_inline_script_by_hash endpoint - Fix duplicate lines from prior commit in run_inline_script_by_path - Change tag from "inline_preview" to "inline" for deployed scripts Co-authored-by: Diego Imbert * Integration tests * rm * rename feature to run_inline * Run inline integration tests * Fix tests * check path scope * openapi fix * nits * remove register_potential_assets_on_inline_execution * unused variable * refactor * Pass user_db to check script permission --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Diego Imbert --- .github/workflows/backend-test.yml | 2 +- backend/Cargo.lock | 1 + backend/Cargo.toml | 4 +- backend/windmill-api-configs/Cargo.toml | 2 +- backend/windmill-api-configs/src/lib.rs | 6 +- .../windmill-api-integration-tests/Cargo.toml | 2 + .../tests/run_inline.rs | 258 ++++++++++++++++++ backend/windmill-api/Cargo.toml | 2 +- backend/windmill-api/openapi.yaml | 54 ++++ backend/windmill-api/src/jobs.rs | 141 +++++++++- backend/windmill-common/src/db.rs | 15 + backend/windmill-common/src/jobs.rs | 29 ++ backend/windmill-queue/src/jobs.rs | 53 ++++ backend/windmill-test-utils/Cargo.toml | 2 + backend/windmill-worker/src/worker.rs | 133 ++++++--- 15 files changed, 648 insertions(+), 56 deletions(-) create mode 100644 backend/windmill-api-integration-tests/tests/run_inline.rs diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index f137f1323c..94967feb4c 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -238,4 +238,4 @@ jobs: run: | deno --version && bun -v && node --version && go version && python3 --version && php --version && ruby --version && pwsh --version && dotnet --version cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. - DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp --all -- --nocapture --test-threads=10 + DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp,run_inline --all -- --nocapture --test-threads=10 diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 90400b04e0..1238c47b05 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -16167,6 +16167,7 @@ dependencies = [ "windmill-common", "windmill-native-triggers", "windmill-test-utils", + "windmill-worker", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ef969c8ffe..c08092556d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -159,12 +159,12 @@ all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", " # For windows we have another set of languages enabled all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql-winauth", "bigquery", "csharp", "nu", "php", "java"] # Edition meta-features: shared groups -inline_preview = ["windmill-api/inline_preview"] +run_inline = ["windmill-api/run_inline"] oss_core = [ "embedding", "parquet", "openidconnect", "license", "http_trigger", "zip", "oauth2", "postgres_trigger", "mqtt_trigger", "websocket", "smtp", "native_trigger", - "static_frontend", "mcp", "bedrock", "inline_preview", + "static_frontend", "mcp", "bedrock", "run_inline", "quickjs" ] ce_core = ["oss_core", "private", "operator"] diff --git a/backend/windmill-api-configs/Cargo.toml b/backend/windmill-api-configs/Cargo.toml index bfb702c73b..a24e0b9540 100644 --- a/backend/windmill-api-configs/Cargo.toml +++ b/backend/windmill-api-configs/Cargo.toml @@ -13,7 +13,7 @@ default = [] enterprise = ["dep:windmill-autoscaling"] private = [] python = [] -inline_preview = ["dep:windmill-worker", "dep:itertools"] +run_inline = ["dep:windmill-worker", "dep:itertools"] [dependencies] windmill-api-auth.workspace = true diff --git a/backend/windmill-api-configs/src/lib.rs b/backend/windmill-api-configs/src/lib.rs index 59ad1625a4..f4e91e554f 100644 --- a/backend/windmill-api-configs/src/lib.rs +++ b/backend/windmill-api-configs/src/lib.rs @@ -283,14 +283,14 @@ async fn native_kubernetes_autoscaling_healthcheck() -> Result<(), error::Error> } async fn list_available_python_versions() -> error::JsonResult> { - #[cfg(not(all(feature = "python", feature = "inline_preview")))] + #[cfg(not(all(feature = "python", feature = "run_inline")))] return Err(error::Error::BadRequest( "Python listing available only with 'python' feature enabled".to_string(), )); - #[cfg(all(feature = "python", feature = "inline_preview"))] + #[cfg(all(feature = "python", feature = "run_inline"))] use itertools::Itertools; - #[cfg(all(feature = "python", feature = "inline_preview"))] + #[cfg(all(feature = "python", feature = "run_inline"))] return Ok(Json( windmill_worker::PyV::list_available_python_versions() .await diff --git a/backend/windmill-api-integration-tests/Cargo.toml b/backend/windmill-api-integration-tests/Cargo.toml index c27a10775e..c7d608ed28 100644 --- a/backend/windmill-api-integration-tests/Cargo.toml +++ b/backend/windmill-api-integration-tests/Cargo.toml @@ -14,6 +14,7 @@ private = ["windmill-test-utils/private", "dep:aws-config", "dep:aws-credential- enterprise = ["windmill-test-utils/enterprise", "dep:base64"] deno_core = ["windmill-test-utils/deno_core"] mcp = [] +run_inline = ["dep:windmill-worker", "windmill-test-utils/run_inline", "windmill-test-utils/duckdb"] [dependencies] windmill-test-utils.workspace = true @@ -21,6 +22,7 @@ windmill-api-client.workspace = true windmill-common = { workspace = true, default-features = false } windmill-native-triggers = { workspace = true, features = ["native_trigger"] } windmill-api-auth.workspace = true +windmill-worker = { workspace = true, optional = true } sqlx.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/backend/windmill-api-integration-tests/tests/run_inline.rs b/backend/windmill-api-integration-tests/tests/run_inline.rs new file mode 100644 index 0000000000..cbbd25b470 --- /dev/null +++ b/backend/windmill-api-integration-tests/tests/run_inline.rs @@ -0,0 +1,258 @@ +use serde_json::json; +use sqlx::{Pool, Postgres}; + +use windmill_test_utils::*; + +#[cfg(feature = "run_inline")] +async fn init_inline_utils(port: u16) -> anyhow::Result<()> { + use std::sync::Once; + static INIT: Once = Once::new(); + + INIT.call_once(|| { + let (killpill_tx, killpill_rx) = windmill_common::KillpillSender::new(1); + let base_internal_url = format!("http://localhost:{}", port); + windmill_worker::init_worker_internal_server_inline_utils(killpill_rx, base_internal_url) + .expect("Failed to initialize inline utils"); + // Keep killpill_tx alive for the test duration + std::mem::forget(killpill_tx); + }); + + Ok(()) +} + +fn run_inline_url(port: u16, endpoint: &str) -> String { + format!("http://localhost:{port}/api/w/test-workspace/jobs/run_inline/{endpoint}") +} + +fn client() -> reqwest::Client { + reqwest::Client::new() +} + +fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder { + builder.header("Authorization", "Bearer SECRET_TOKEN") +} + +fn new_script( + path: &str, + summary: &str, + content: &str, + language: &str, + schema_properties: serde_json::Value, +) -> serde_json::Value { + json!({ + "path": path, + "summary": summary, + "description": "", + "content": content, + "language": language, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": schema_properties, + "required": [] + } + }) +} + +#[cfg(feature = "run_inline")] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] +async fn test_run_inline_by_path(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + // Initialize inline utils for script execution + init_inline_utils(port).await?; + + // Create a DuckDB script (one of the languages that supports inline execution) + // DuckDB requires parameter declarations in comments: -- $param_name (type) + let script_path = "u/test-user/inline_test"; + let script_content = "-- $x (integer) +-- $y (integer) +SELECT $x + $y as result"; + + let resp = authed(client().post(format!("{base}/scripts/create"))) + .json(&new_script( + script_path, + "Inline test script", + script_content, + "duckdb", + json!({ + "x": {"type": "integer"}, + "y": {"type": "integer"} + }), + )) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 201, "create script: {}", resp.text().await?); + + // Test run_inline by path with args + let resp = authed(client().post(run_inline_url(port, &format!("p/{script_path}")))) + .json(&json!({ + "args": { + "x": 5, + "y": 15 + } + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200, "run_inline by path with args failed"); + let result = resp.json::().await?; + // DuckDB query should return array with one row containing result field + // The result structure is [{"result": 20}] + assert!(result.is_array(), "expected array result, got: {}", result); + let rows = result.as_array().unwrap(); + assert_eq!(rows.len(), 1, "expected 1 row, got: {}", rows.len()); + assert_eq!( + rows[0]["result"], + json!(20), + "expected result 20, got: {}", + rows[0] + ); + + Ok(()) +} + +#[cfg(feature = "run_inline")] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] +async fn test_run_inline_by_hash(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + // Initialize inline utils for script execution + init_inline_utils(port).await?; + + // Create a DuckDB script and get its hash + let script_path = "u/test-user/inline_hash_test"; + let script_content = "-- $a (integer) +-- $b (integer) +SELECT $a * $b as product"; + + let resp = authed(client().post(format!("{base}/scripts/create"))) + .json(&new_script( + script_path, + "Inline hash test script", + script_content, + "duckdb", + json!({ + "a": {"type": "integer"}, + "b": {"type": "integer"} + }), + )) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 201, "create script: {}", resp.text().await?); + + // Get the script to retrieve its hash + let resp = authed(client().get(format!("{base}/scripts/get/p/{script_path}"))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let script_data = resp.json::().await?; + let hash = script_data["hash"] + .as_str() + .expect("hash should be present"); + + // Test run_inline by hash with args + let resp = authed(client().post(run_inline_url(port, &format!("h/{hash}")))) + .json(&json!({ + "args": { + "a": 7, + "b": 3 + } + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200, "run_inline by hash with args failed"); + let result = resp.json::().await?; + // Should return array with one row: [{"product": 21}] + assert!(result.is_array(), "expected array result"); + let rows = result.as_array().unwrap(); + assert_eq!(rows.len(), 1, "expected 1 row"); + assert_eq!( + rows[0]["product"], + json!(21), + "expected product 21, got: {}", + rows[0] + ); + + Ok(()) +} + +#[cfg(feature = "run_inline")] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] +async fn test_run_inline_preview(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + // Initialize inline utils for script execution + init_inline_utils(port).await?; + + // Test run_inline preview with direct DuckDB content + let resp = authed(client().post(run_inline_url(port, "preview"))) + .json(&json!({ + "content": "-- $msg (text)\nSELECT 'Hello, ' || $msg || '!' as greeting", + "language": "duckdb", + "args": { + "msg": "World" + } + })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200, "run_inline preview failed"); + let result = resp.json::().await?; + // Should return array with one row: [{"greeting": "Hello, World!"}] + assert!(result.is_array(), "expected array result"); + let rows = result.as_array().unwrap(); + assert_eq!(rows.len(), 1, "expected 1 row"); + assert_eq!( + rows[0]["greeting"], + json!("Hello, World!"), + "expected 'Hello, World!', got: {}", + rows[0] + ); + + Ok(()) +} + +#[cfg(feature = "run_inline")] +#[sqlx::test(migrations = "../migrations", fixtures("base"))] +async fn test_run_inline_nonexistent_script(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + // Initialize inline utils + init_inline_utils(port).await?; + + // Test run_inline by path with non-existent script - should return an error + let resp = authed(client().post(run_inline_url(port, "p/u/test-user/nonexistent_script"))) + .json(&json!({ + "args": null + })) + .send() + .await + .unwrap(); + + // Should return an error (script not found) + assert!( + resp.status().is_client_error() || resp.status().is_server_error(), + "expected error status for nonexistent script, got: {}", + resp.status() + ); + + Ok(()) +} diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index 66b002043e..7af8137630 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -13,7 +13,7 @@ default = [] private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"] enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"] stripe = [] -inline_preview = ["dep:windmill-worker", "windmill-api-configs/inline_preview"] +run_inline = ["dep:windmill-worker", "windmill-api-configs/run_inline"] agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"] enterprise_saml = ["dep:samael", "dep:libxml"] benchmark = [] diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index dd65cf9e2f..dbf058e4cb 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -9173,6 +9173,54 @@ paths: application/json: schema: {} + /w/{workspace}/jobs/run_inline/p/{path}: + post: + summary: run script by path without starting a new job + operationId: runScriptByPathInline + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptPath" + requestBody: + description: script args + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InlineScriptArgs" + + responses: + "200": + description: script result + content: + application/json: + schema: {} + + /w/{workspace}/jobs/run_inline/h/{hash}: + post: + summary: run script by hash without starting a new job + operationId: runScriptByHashInline + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/ScriptHash" + requestBody: + description: script args + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InlineScriptArgs" + + responses: + "200": + description: script result + content: + application/json: + schema: {} + /w/{workspace}/jobs/run_wait_result/preview: post: summary: run script preview and wait for result @@ -19811,6 +19859,12 @@ components: $ref: "#/components/schemas/ScriptLang" required: [content, args, language] + InlineScriptArgs: + type: object + properties: + args: + $ref: "#/components/schemas/ScriptArgs" + WorkflowTask: type: object properties: diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 077ddd2876..2528e2556f 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -27,20 +27,22 @@ use url::Url; #[cfg(all(feature = "enterprise", feature = "smtp"))] use windmill_common::auth::is_super_admin_email; use windmill_common::auth::TOKEN_PREFIX_LEN; -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] use windmill_common::client::AuthedClient; use windmill_common::db::UserDbWithAuthed; use windmill_common::error::JsonResult; use windmill_common::flow_status::{JobResult, RestartedFrom}; -#[cfg(feature = "inline_preview")] -use windmill_common::jobs::RunInlinePreviewScriptFnParams; use windmill_common::jobs::{ format_completed_job_result, format_result, DynamicInput, ENTRYPOINT_OVERRIDE, }; +#[cfg(feature = "run_inline")] +use windmill_common::jobs::{ + InlineScriptTarget, RunInlinePreviewScriptFnParams, RunInlineScriptFnParams, +}; use windmill_common::runnable_settings::{ ConcurrencySettings, ConcurrencySettingsWithCustom, DebouncingSettings, }; -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] use windmill_common::runtime_assets::{register_runtime_asset, InsertRuntimeAssetParams}; use windmill_common::scripts::ScriptRunnableSettingsInline; use windmill_common::triggers::TriggerMetadata; @@ -53,15 +55,15 @@ use windmill_common::DYNAMIC_INPUT_CACHE; #[cfg(all(feature = "enterprise", feature = "smtp"))] use windmill_common::{email_oss::send_email_html, server::load_smtp_config}; use windmill_object_store::upload_artifact_to_store; -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] use windmill_parser::asset_parser::AssetKind; use windmill_types::s3::BundleFormat; -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] use windmill_worker::get_worker_internal_server_inline_utils; use windmill_common::variables::get_workspace_key; -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] use crate::db::OptJobAuthed; use crate::triggers::trigger_helpers::{FlowId, ScriptId}; use crate::{ @@ -242,6 +244,11 @@ pub fn workspaced_service() -> Router { ) .route("/run/preview", post(run_preview_script)) .route("/run_inline/preview", post(run_inline_preview_script)) + .route( + "/run_inline/p/*script_path", + post(run_inline_script_by_path), + ) + .route("/run_inline/h/:hash", post(run_inline_script_by_hash)) .route( "/run_wait_result/preview", post(run_wait_result_preview_script), @@ -2853,7 +2860,7 @@ struct Preview { flow_path: Option, } -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] #[derive(Debug, Deserialize)] struct PreviewInline { content: String, @@ -2861,6 +2868,12 @@ struct PreviewInline { language: ScriptLang, } +#[cfg(feature = "run_inline")] +#[derive(Debug, Deserialize)] +struct InlineScriptArgs { + args: Option>>, +} + #[derive(Deserialize)] pub struct WorkflowTask { pub args: Option>>, @@ -4573,7 +4586,7 @@ async fn run_preview_script( Ok((StatusCode::CREATED, uuid.to_string())) } -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] async fn run_inline_preview_script( OptJobAuthed { authed, job_id }: OptJobAuthed, Tokened { token }: Tokened, @@ -4610,14 +4623,120 @@ async fn run_inline_preview_script( Ok(Json(to_raw_value(&result)).into_response()) } -#[cfg(not(feature = "inline_preview"))] +#[cfg(not(feature = "run_inline"))] async fn run_inline_preview_script() -> error::Result { Err(error::Error::InternalErr( "inline preview requires the worker feature".to_string(), )) } -#[cfg(feature = "inline_preview")] +#[cfg(feature = "run_inline")] +async fn run_inline_script_by_path( + OptJobAuthed { authed, .. }: OptJobAuthed, + Tokened { token }: Tokened, + Extension(db): Extension, + Extension(user_db): Extension, + Path((w_id, script_path)): Path<(String, StripPath)>, + Json(body): Json, +) -> error::Result { + let script_path_str = script_path.to_path(); + check_scopes(&authed, || format!("jobs:run:scripts:{script_path_str}"))?; + run_inline_script_inner( + authed, + token, + db, + w_id, + InlineScriptTarget::Path(script_path.to_path().to_string()), + body.args, + Some(user_db), + ) + .await +} + +#[cfg(not(feature = "run_inline"))] +async fn run_inline_script_by_path() -> error::Result { + Err(error::Error::InternalErr( + "inline script by path requires the worker feature".to_string(), + )) +} + +#[cfg(feature = "run_inline")] +async fn run_inline_script_by_hash( + OptJobAuthed { authed, .. }: OptJobAuthed, + Tokened { token }: Tokened, + Extension(db): Extension, + Extension(user_db): Extension, + Path((w_id, script_hash)): Path<(String, ScriptHash)>, + Json(body): Json, +) -> error::Result { + // Resolve the script path from the hash and check scopes properly + let hash = script_hash.0; + let userdb_authed = UserDbWithAuthed { db: user_db.clone(), authed: &authed.to_authed_ref() }; + let ScriptHashInfo { path, .. } = + get_script_info_for_hash(Some(userdb_authed), &db, &w_id, hash) + .await? + .prefetch_cached(&db) + .await?; + + check_scopes(&authed, || format!("jobs:run:scripts:{path}"))?; + + run_inline_script_inner( + authed, + token, + db, + w_id, + InlineScriptTarget::Hash(hash), + body.args, + Some(user_db), + ) + .await +} + +#[cfg(not(feature = "run_inline"))] +async fn run_inline_script_by_hash() -> error::Result { + Err(error::Error::InternalErr( + "inline script by hash requires the worker feature".to_string(), + )) +} + +#[cfg(feature = "run_inline")] +async fn run_inline_script_inner( + authed: ApiAuthed, + token: String, + db: DB, + w_id: String, + target: InlineScriptTarget, + args: Option>>, + user_db: Option, +) -> error::Result { + let utils = get_worker_internal_server_inline_utils()?; + let authed_owned: windmill_common::db::Authed = authed.clone().into(); + let result = utils.run_inline_script.as_ref()(RunInlineScriptFnParams { + target, + args, + workspace_id: w_id.clone(), + base_internal_url: utils.base_internal_url.clone(), + killpill_rx: utils.killpill_rx.resubscribe(), + created_by: authed.display_username().to_string(), + permissioned_as: username_to_permissioned_as(&authed.username), + permissioned_as_email: authed.email.clone(), + job_dir: "".to_string(), + worker_name: "".to_string(), + worker_dir: "".to_string(), + client: AuthedClient { + base_internal_url: utils.base_internal_url.clone(), + force_client: None, + token, + workspace: w_id, + }, + conn: windmill_common::worker::Connection::Sql(db), + user_db: user_db.map(|udb| (udb, authed_owned)), + }) + .await?; + Ok(Json(to_raw_value(&result)).into_response()) +} + +#[cfg(feature = "run_inline")] fn register_potential_assets_on_inline_execution( job_id: Uuid, w_id: &str, diff --git a/backend/windmill-common/src/db.rs b/backend/windmill-common/src/db.rs index cf938504d3..da1d4c1cd6 100644 --- a/backend/windmill-common/src/db.rs +++ b/backend/windmill-common/src/db.rs @@ -17,6 +17,21 @@ pub struct Authed { pub token_prefix: Option, } +impl Authed { + pub fn to_authed_ref(&self) -> AuthedRef<'_> { + AuthedRef { + email: &self.email, + username: &self.username, + is_admin: &self.is_admin, + is_operator: &self.is_operator, + groups: &self.groups, + folders: &self.folders, + scopes: &self.scopes, + token_prefix: &self.token_prefix, + } + } +} + #[derive(Clone, Debug, Hash)] pub struct AuthedRef<'a> { pub email: &'a str, diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index 12004b945d..bc40e4cd75 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -326,6 +326,28 @@ pub struct RunInlinePreviewScriptFnParams { pub killpill_rx: tokio::sync::broadcast::Receiver<()>, } +pub enum InlineScriptTarget { + Path(String), + Hash(i64), +} + +pub struct RunInlineScriptFnParams { + pub workspace_id: String, + pub target: InlineScriptTarget, + pub args: Option>>, + pub created_by: String, + pub permissioned_as: String, + pub permissioned_as_email: String, + pub base_internal_url: String, + pub worker_name: String, + pub conn: crate::worker::Connection, + pub client: AuthedClient, + pub job_dir: String, + pub worker_dir: String, + pub killpill_rx: tokio::sync::broadcast::Receiver<()>, + pub user_db: Option<(crate::db::UserDB, crate::db::Authed)>, +} + #[derive(Clone)] pub struct WorkerInternalServerInlineUtils { pub killpill_rx: Arc>, @@ -337,6 +359,13 @@ pub struct WorkerInternalServerInlineUtils { + Send + Sync, >, + pub run_inline_script: Arc< + dyn Fn( + RunInlineScriptFnParams, + ) -> Pin>> + Send>> + + Send + + Sync, + >, } // To run a script inline, bypassing the db and job queue, windmill-api uses these functions. // They should only be called by the internal server of a worker. diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 007b398c79..806e3e99a2 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -2285,6 +2285,59 @@ pub struct MiniPulledJob { pub runnable_settings_handle: Option, } +impl MiniPulledJob { + pub fn new_inline( + workspace_id: String, + args: Option>>, + created_by: String, + permissioned_as: String, + permissioned_as_email: String, + runnable_path: Option, + kind: JobKind, + runnable_id: Option, + tag: String, + script_lang: Option, + ) -> Self { + Self { + workspace_id, + id: Uuid::new_v4(), + args: args.map(Json), + parent_job: None, + created_by, + scheduled_for: chrono::Utc::now(), + started_at: None, + runnable_path, + kind, + runnable_id, + canceled_reason: None, + canceled_by: None, + permissioned_as, + permissioned_as_email, + flow_status: None, + tag, + script_lang, + same_worker: true, + pre_run_error: None, + flow_innermost_root_job: None, + root_job: None, + timeout: None, + flow_step_id: None, + cache_ttl: None, + cache_ignore_s3_path: None, + priority: None, + preprocessed: None, + script_entrypoint_override: None, + trigger: None, + trigger_kind: None, + visible_to_owner: false, + permissioned_as_end_user_email: None, + runnable_settings_handle: None, + concurrent_limit: None, + concurrency_time_window_s: None, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MiniCompletedJob { pub id: Uuid, diff --git a/backend/windmill-test-utils/Cargo.toml b/backend/windmill-test-utils/Cargo.toml index 3d253a6772..e805ee5a3d 100644 --- a/backend/windmill-test-utils/Cargo.toml +++ b/backend/windmill-test-utils/Cargo.toml @@ -15,6 +15,8 @@ enterprise = [] python = ["windmill-common/python"] deno_core = ["dep:windmill-runtime-nativets"] agent_worker_server = ["dep:windmill-api-agent-workers"] +run_inline = ["windmill-api/run_inline"] +duckdb = ["windmill-worker/duckdb"] [dependencies] windmill-api = { workspace = true, default-features = false } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 6f14512b32..c354d34ee2 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -14,6 +14,10 @@ use futures::TryFutureExt; use tokio::sync::Mutex; use tokio::time::timeout; use windmill_common::client::AuthedClient; +use windmill_common::db::UserDbWithAuthed; +use windmill_common::get_latest_deployed_hash_for_path; +use windmill_common::jobs::InlineScriptTarget; +use windmill_common::jobs::RunInlineScriptFnParams; use windmill_common::jobs::WorkerInternalServerInlineUtils; use windmill_common::jobs::WORKER_INTERNAL_SERVER_INLINE_UTILS; use windmill_common::runtime_assets::init_runtime_asset_loop; @@ -4705,43 +4709,18 @@ pub fn init_worker_internal_server_inline_utils( base_internal_url, killpill_rx: Arc::new(killpill_rx), run_inline_preview_script: Arc::new(|params| { - let job = MiniPulledJob { - workspace_id: params.workspace_id, - id: Uuid::new_v4(), - args: params.args.map(Json), - parent_job: None, - created_by: params.created_by, - scheduled_for: chrono::Utc::now(), - started_at: None, - runnable_path: None, - kind: JobKind::Preview, - runnable_id: None, - canceled_reason: None, - canceled_by: None, - permissioned_as: params.permissioned_as, - permissioned_as_email: params.permissioned_as_email, - flow_status: None, - tag: "inline_preview".to_string(), - script_lang: Some(params.lang), - same_worker: true, - pre_run_error: None, - flow_innermost_root_job: None, - root_job: None, - timeout: None, - flow_step_id: None, - cache_ttl: None, - cache_ignore_s3_path: None, - priority: None, - preprocessed: None, - script_entrypoint_override: None, - trigger: None, - trigger_kind: None, - visible_to_owner: false, - permissioned_as_end_user_email: None, - runnable_settings_handle: None, - concurrent_limit: None, - concurrency_time_window_s: None, - }; + let job = MiniPulledJob::new_inline( + params.workspace_id, + params.args, + params.created_by, + params.permissioned_as, + params.permissioned_as_email, + None, + JobKind::Preview, + None, + "inline_preview".to_string(), + Some(params.lang), + ); Box::pin(async move { let mut mem_peak: i32 = -1; let mut canceled_by: Option = None; @@ -4778,6 +4757,86 @@ pub fn init_worker_internal_server_inline_utils( .await }) }), + run_inline_script: Arc::new(|params: RunInlineScriptFnParams| { + Box::pin(async move { + let (script_hash, runnable_path) = match params.target { + InlineScriptTarget::Path(ref path) => { + let db = params + .conn + .as_sql() + .ok_or_else(|| { + error::Error::InternalErr( + "run_inline_script by path requires a SQL connection" + .to_string(), + ) + })? + .clone(); + let authed_ref = params.user_db.as_ref().map(|(_, a)| a.to_authed_ref()); + let user_db_authed = + params.user_db.as_ref().zip(authed_ref.as_ref()).map( + |((udb, _), ar)| UserDbWithAuthed { db: udb.clone(), authed: ar }, + ); + let script_hash_info = get_latest_deployed_hash_for_path( + user_db_authed, + db, + ¶ms.workspace_id, + path, + ) + .await?; + (ScriptHash(script_hash_info.hash), Some(path.clone())) + } + InlineScriptTarget::Hash(hash) => (ScriptHash(hash), None), + }; + let content_info = + get_script_content_by_hash(&script_hash, ¶ms.workspace_id, ¶ms.conn) + .await?; + let job = MiniPulledJob::new_inline( + params.workspace_id, + params.args, + params.created_by, + params.permissioned_as, + params.permissioned_as_email, + runnable_path, + JobKind::Script, + Some(script_hash), + "inline_run".to_string(), + content_info.language, + ); + let mut mem_peak: i32 = -1; + let mut canceled_by: Option = None; + let mut column_order: Option> = None; + let mut new_args: Option>> = None; + let mut occupancy_metrics = OccupancyMetrics::new(Instant::now()); + let mut has_stream: bool = false; + let mut killpill_rx = params.killpill_rx; + + run_language_executor( + &job, + ¶ms.conn, + ¶ms.client, + None, + ¶ms.job_dir, + ¶ms.worker_dir, + &mut mem_peak, + &mut canceled_by, + ¶ms.base_internal_url, + ¶ms.worker_name, + &mut column_order, + &mut new_args, + &mut occupancy_metrics, + &mut killpill_rx, + None, + &mut has_stream, + content_info.language, + &content_info.content, + &content_info.envs, + &content_info.codebase, + &content_info.lockfile, + true, + ) + .await + }) + }), }; WORKER_INTERNAL_SERVER_INLINE_UTILS .set(utils) From 7728475fc9ab2a1bda1117c6d248085150ca56e0 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Sat, 28 Feb 2026 07:51:02 +0100 Subject: [PATCH 04/35] refactor: rewrite flake.nix for clarity and modularity (#8137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: rewrite flake.nix from scratch for clarity and modularity Rewrite the Nix flake with clean separation of concerns, organized let-bindings, and 4 purpose-specific devShells instead of a monolithic default shell with broken package outputs. Co-Authored-By: Claude Opus 4.6 * feat: add CLI tools to default devShell (gh, aws, playwright, mermaid, asciinema) Add tools needed for AI agent workflows and dev tooling: - gh (GitHub CLI) - awscli2 - asciinema (terminal recording) - playwright-driver with Nix-managed browsers - mermaid-cli (diagram generation) Playwright browsers are provided via nixpkgs' playwright-driver.browsers. Mermaid/Puppeteer reuses the headless_shell from the same browser set. Co-Authored-By: Claude Opus 4.6 * fix: move wm-minio scripts to default devShell MinIO (local S3) is needed for regular development, not just the full profile. Co-Authored-By: Claude Opus 4.6 * fix: use playwright wrapper + chromium for browser tools Replace playwright-driver (library, no CLI) with: - A `playwright` wrapper script that calls the Nix playwright-core CLI (version-matched to its own Nix-provided browsers) - pkgs.chromium for Mermaid/Puppeteer (which respects PUPPETEER_EXECUTABLE_PATH) This fixes playwright screenshot and mermaid diagram generation. Co-Authored-By: Claude Opus 4.6 * feat: auto-load .env.local from main worktree in all devShells Gitignored files like .env.local don't exist in git worktrees. Add a shared shellHook that resolves back to the main tree via git-common-dir and sources .env.local if present. This ensures AWS credentials and other secrets are available in worktrees. Co-Authored-By: Claude Opus 4.6 * fix: replace deprecated pkgs.hostPlatform with stdenv.hostPlatform Co-Authored-By: Claude Opus 4.6 * chore: remove AWS CLI from flake and sandbox images Pastebin is sufficient for screenshot sharing; AWS credentials add unnecessary complexity. Co-Authored-By: Claude Opus 4.6 * fix: address PR review — ruby mismatch, quoting, shell dedup - Fix pkgs.ruby → pkgs.ruby_3_4 in extraRuntimeVars to match extraRuntimes - Replace $* with "$@" in all helper scripts (wm, wm-build, wm-caddy, wm-bench, wm-cli) to correctly preserve argument boundaries - Extract coreBuildInputs, browserVars, and playwrightWrapper as shared let-bindings to eliminate duplication between default and full shells Co-Authored-By: Claude Opus 4.6 * chore: remove .env.local auto-loading from devShells Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- Dockerfile.sandbox | 7 - flake.nix | 760 ++++++++++++++++--------------- sandbox-image/Dockerfile.sandbox | 7 - 3 files changed, 381 insertions(+), 393 deletions(-) diff --git a/Dockerfile.sandbox b/Dockerfile.sandbox index 230244de95..64aa39855e 100644 --- a/Dockerfile.sandbox +++ b/Dockerfile.sandbox @@ -195,13 +195,6 @@ RUN bun add -g @playwright/test \ && chmod -R a+rwX /usr/local/lib/bun/install \ && rm -rf /var/lib/apt/lists/* /tmp/bunx-* -# ── AWS CLI (for S3-compatible uploads to R2) ───────────────────────────────── -RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \ - && unzip -q /tmp/awscliv2.zip -d /tmp \ - && /tmp/aws/install \ - && rm -rf /tmp/aws /tmp/awscliv2.zip - -ENV AWS_DEFAULT_REGION=auto # ── Runtime env for arbitrary UID ───────────────────────────────────────────── # Mutable state goes to /tmp (writable by any UID). Toolchains stay read-only. diff --git a/flake.nix b/flake.nix index 92fca24cdd..34ecb689a1 100644 --- a/flake.nix +++ b/flake.nix @@ -3,11 +3,11 @@ nixpkgs.url = "nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay.url = "github:oxalica/rust-overlay"; - nixpkgs-oapi-gen.url = - "nixpkgs/2d068ae5c6516b2d04562de50a58c682540de9bf"; # openapi-generator-cli pin to 7.10.0 + # Pin openapi-generator-cli to 7.10.0 + nixpkgs-oapi-gen.url = "nixpkgs/2d068ae5c6516b2d04562de50a58c682540de9bf"; }; - outputs = { self, nixpkgs, flake-utils, rust-overlay - , nixpkgs-oapi-gen }: + + outputs = { self, nixpkgs, flake-utils, rust-overlay, nixpkgs-oapi-gen }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { @@ -16,47 +16,180 @@ overlays = [ (import rust-overlay) ]; }; - openapi-generator-cli = - (import nixpkgs-oapi-gen { inherit system; }).openapi-generator-cli; - lib = pkgs.lib; stdenv = pkgs.stdenv; - rust = pkgs.rust-bin.stable.latest.default.override { - extensions = [ - "rust-src" # for rust-analyzer - "rust-analyzer" - "rustfmt" - ]; - }; - patchedClang = pkgs.llvmPackages_18.clang.overrideAttrs (oldAttrs: { - postFixup = '' - # Copy the original postFixup logic but skip add-hardening.sh - ${oldAttrs.postFixup or ""} - # Remove the line that substitutes add-hardening.sh - sed -i 's/.*source.*add-hardening\.sh.*//' $out/bin/clang - ''; - }); - buildInputs = with pkgs; [ + # --------------------------------------------------------------- + # Rust toolchain + # --------------------------------------------------------------- + + rustStable = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" "rustfmt" ]; + }; + + # --------------------------------------------------------------- + # Native C/C++ dependencies (required to compile the backend) + # --------------------------------------------------------------- + + nativeBuildDeps = with pkgs; [ + # Crypto / TLS openssl openssl.dev + + # XML / SAML (enterprise_saml feature) libxml2.dev xmlsec.dev libxslt.dev + + # FFI / codegen libclang.dev + libffi # deno_ffi on macOS + + # Networking / compression curl.dev zlib.dev - libffi # For deno_ffi - libtool - nodejs - postgresql - pkg-config - llvmPackages_18.clang - mold - cmake + + # Auth (kafka-gssapi, mssql-kerberos) cyrus_sasl krb5 + + # Misc + libtool + postgresql + + # Build tooling + pkg-config + llvmPackages_18.clang # linker — pinned to 18 to avoid SIGSEGV with mold + newer clang + mold + cmake # required by rdkafka cmake-build ]; + + # --------------------------------------------------------------- + # Prebuilt V8 binary (must match version in Cargo.toml) + # --------------------------------------------------------------- + + rustyV8Archive = let + version = "130.0.7"; + target = stdenv.hostPlatform.rust.rustcTarget; + sha256 = { + x86_64-linux = "sha256-pkdsuU6bAkcIHEZUJOt5PXdzK424CEgTLXjLtQ80t10="; + aarch64-linux = lib.fakeHash; + x86_64-darwin = lib.fakeHash; + aarch64-darwin = lib.fakeHash; + }.${system}; + in pkgs.fetchurl { + name = "librusty_v8-${version}"; + url = "https://github.com/denoland/rusty_v8/releases/download/v${version}/librusty_v8_release_${target}.a.gz"; + inherit sha256; + }; + + # --------------------------------------------------------------- + # pkg-config search path for native libraries + # --------------------------------------------------------------- + + pkgConfigPath = lib.makeSearchPath "lib/pkgconfig" + (with pkgs; [ openssl.dev libxml2.dev xmlsec.dev libxslt.dev cyrus_sasl.dev krb5.dev ]); + + # --------------------------------------------------------------- + # RPATH — embed Nix store library paths into compiled binaries + # --------------------------------------------------------------- + + rpathLibs = lib.makeLibraryPath (with pkgs; [ + openssl libffi cyrus_sasl krb5 libxml2 xmlsec libxslt stdenv.cc.cc.lib + ]); + + # --------------------------------------------------------------- + # Bindgen configuration + # Bindgen uses libclang directly (not $CC), so we must explicitly + # provide all Nix header search paths. + # See: https://web.archive.org/web/20220523141208/https://hoverbear.org/blog/rust-bindgen-in-nix/ + # --------------------------------------------------------------- + + bindgenClangArgs = builtins.concatStringsSep " " ([ + "-nostdinc" + (builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags") + (builtins.readFile "${stdenv.cc}/nix-support/libc-cflags") + (builtins.readFile "${stdenv.cc}/nix-support/cc-cflags") + (builtins.readFile "${stdenv.cc}/nix-support/libcxx-cxxflags") + "-idirafter ${pkgs.libiconv}/include" + ] ++ lib.optionals stdenv.cc.isClang [ + "-idirafter ${stdenv.cc.cc}/lib/clang/${lib.getVersion stdenv.cc.cc}/include" + ] ++ lib.optionals stdenv.cc.isGNU [ + "-isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc}" + "-isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc}/${stdenv.hostPlatform.config}" + "-idirafter ${stdenv.cc.cc}/lib/gcc/${stdenv.hostPlatform.config}/${lib.getVersion stdenv.cc.cc}/include" + ]); + + # --------------------------------------------------------------- + # Build environment variables (shared by all shells that compile Rust) + # --------------------------------------------------------------- + + buildEnvVars = { + PKG_CONFIG_PATH = pkgConfigPath; + RUSTY_V8_ARCHIVE = rustyV8Archive; + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + BINDGEN_EXTRA_CLANG_ARGS = bindgenClangArgs; + + # Force clang 18 as cargo linker (stdenv may bring a newer clang that causes SIGSEGV with mold) + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER = "${pkgs.llvmPackages_18.clang}/bin/clang"; + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = "${pkgs.llvmPackages_18.clang}/bin/clang"; + + # Embed rpath so binaries find Nix store .so files at runtime + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS = "-C link-arg=-fuse-ld=mold -C link-arg=-Wl,-rpath,${rpathLibs}"; + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS = "-C link-arg=-fuse-ld=mold -C link-arg=-Wl,-rpath,${rpathLibs}"; + CARGO_HOST_RUSTFLAGS = "-C link-arg=-Wl,-rpath,${rpathLibs}"; + + # https://github.com/NixOS/nixpkgs/issues/370494 — jemalloc build fix + CFLAGS = "-Wno-error=int-conversion"; + + LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.zlib ]; + }; + + # --------------------------------------------------------------- + # OpenAPI generator (pinned) + # --------------------------------------------------------------- + + openapi-generator-cli = + (import nixpkgs-oapi-gen { inherit system; }).openapi-generator-cli; + + # --------------------------------------------------------------- + # Common worker runtimes (languages the worker executes) + # --------------------------------------------------------------- + + commonRuntimes = with pkgs; [ + deno + python3 + python3Packages.pip + uv + go + bun + nushell + typescript + flock + ]; + + # --------------------------------------------------------------- + # Runtime PATH env vars — tells the worker where to find interpreters + # --------------------------------------------------------------- + + commonRuntimeVars = { + DENO_PATH = "${pkgs.deno}/bin/deno"; + GO_PATH = "${pkgs.go}/bin/go"; + BUN_PATH = "${pkgs.bun}/bin/bun"; + NODE_PATH = "${pkgs.nodejs}/bin/node"; + NODE_BIN_PATH = "${pkgs.nodejs}/bin/node"; + UV_PATH = "${pkgs.uv}/bin/uv"; + NU_PATH = "${pkgs.nushell}/bin/nu"; + FLOCK_PATH = "${pkgs.flock}/bin/flock"; + CARGO_PATH = "${rustStable}/bin/cargo"; + BASH_PATH = "bash"; + GIT_PATH = "${pkgs.git}/bin/git"; + }; + + # --------------------------------------------------------------- + # Extra language runtimes (full shell only) + # --------------------------------------------------------------- + coursier = pkgs.fetchFromGitHub { owner = "coursier"; repo = "launchers"; @@ -64,67 +197,245 @@ hash = "sha256-8E0WtDFc7RcqmftDigMyy1xXUkjgL4X4kpf7h1GdE48="; }; - PKG_CONFIG_PATH = pkgs.lib.makeSearchPath "lib/pkgconfig" - (with pkgs; [ openssl.dev libxml2.dev xmlsec.dev libxslt.dev cyrus_sasl.dev krb5.dev ]); - RUSTY_V8_ARCHIVE = let - # NOTE: needs to be same as in Cargo.toml - version = "130.0.7"; - target = pkgs.hostPlatform.rust.rustcTarget; - sha256 = { - x86_64-linux = - "sha256-pkdsuU6bAkcIHEZUJOt5PXdzK424CEgTLXjLtQ80t10="; - aarch64-linux = pkgs.lib.fakeHash; - x86_64-darwin = pkgs.lib.fakeHash; - aarch64-darwin = pkgs.lib.fakeHash; - }.${system}; - in pkgs.fetchurl { - name = "librusty_v8-${version}"; - url = - "https://github.com/denoland/rusty_v8/releases/download/v${version}/librusty_v8_release_${target}.a.gz"; - inherit sha256; + extraRuntimes = with pkgs; [ + dotnet-sdk_9 + php + php84Packages.composer + ruby_3_4 + jdk21 + ansible + oracle-instantclient + ]; + + extraRuntimeVars = { + JAVA_PATH = "${pkgs.jdk21}/bin/java"; + JAVAC_PATH = "${pkgs.jdk21}/bin/javac"; + COURSIER_PATH = "${coursier}/coursier"; + DOTNET_PATH = "${pkgs.dotnet-sdk_9}/bin/dotnet"; + DOTNET_ROOT = "${pkgs.dotnet-sdk_9}/share/dotnet"; + PHP_PATH = "${pkgs.php}/bin/php"; + COMPOSER_PATH = "${pkgs.php84Packages.composer}/bin/composer"; + RUBY_PATH = "${pkgs.ruby_3_4}/bin/ruby"; + RUBY_BUNDLE_PATH = "${pkgs.ruby_3_4}/bin/bundle"; + RUBY_GEM_PATH = "${pkgs.ruby_3_4}/bin/gem"; + ORACLE_LIB_DIR = "${pkgs.oracle-instantclient.lib}/lib"; + ANSIBLE_PLAYBOOK_PATH = "${pkgs.ansible}/bin/ansible-playbook"; + ANSIBLE_GALAXY_PATH = "${pkgs.ansible}/bin/ansible-galaxy"; + CARGO_SWEEP_PATH = "${pkgs.cargo-sweep}/bin/cargo-sweep"; }; + + # --------------------------------------------------------------- + # General dev environment variables + # --------------------------------------------------------------- + + devEnvVars = { + DATABASE_URL = "postgres://postgres:changeme@127.0.0.1:5432/windmill?sslmode=disable"; + REMOTE = "http://127.0.0.1:8000"; + REMOTE_LSP = "http://127.0.0.1:3001"; + NODE_ENV = "development"; + NODE_OPTIONS = "--max-old-space-size=16384"; + }; + + # --------------------------------------------------------------- + # Helper scripts — base set (default + full) + # --------------------------------------------------------------- + + helperScriptsBase = [ + (pkgs.writeScriptBin "wm" '' + cd ./frontend + npm install + npm run ${if stdenv.isDarwin then "generate-backend-client-mac" else "generate-backend-client"} + npm run dev "$@" + '') + (pkgs.writeScriptBin "wm-build" '' + cd ./frontend + npm install + npm run ${if stdenv.isDarwin then "generate-backend-client-mac" else "generate-backend-client"} + npm run build "$@" + '') + (pkgs.writeScriptBin "wm-migrate" '' + cd ./backend + sqlx migrate run + '') + (pkgs.writeScriptBin "wm-reset" '' + sqlx database drop -f + sqlx database create + wm-migrate + '') + (pkgs.writeScriptBin "wm-minio" '' + set -e + cd ./backend + mkdir -p .minio-data/wmill + ${pkgs.minio}/bin/minio server ./.minio-data --console-address ":9001" + '') + (pkgs.writeScriptBin "wm-minio-keys" '' + set -e + cd ./backend + ${pkgs.minio-client}/bin/mc alias set 'wmill-minio-dev' 'http://localhost:9000' 'minioadmin' 'minioadmin' + if [[ -f .minio-data/secrets.txt ]] && [[ -s .minio-data/secrets.txt ]]; then + echo "Access keys already exist:" + cat .minio-data/secrets.txt + echo "" + echo "Keys loaded from: ./backend/.minio-data/secrets.txt" + else + echo "Creating new access keys..." + mkdir -p .minio-data + ${pkgs.minio-client}/bin/mc admin accesskey create 'wmill-minio-dev' | tee .minio-data/secrets.txt + echo "" + echo 'New keys saved to: ./backend/.minio-data/secrets.txt' + fi + echo "bucket: wmill" + echo "endpoint: http://localhost:9000" + '') + ]; + + # --------------------------------------------------------------- + # Helper scripts — extra (full shell only) + # --------------------------------------------------------------- + + helperScriptsFull = [ + (pkgs.writeScriptBin "wm-caddy" '' + cd ./frontend + xcaddy build "$@" \ + --with github.com/mholt/caddy-l4@145ec36251a44286f05a10d231d8bfb3a8192e09 \ + --with github.com/RussellLuo/caddy-ext/layer4@ab1e18cfe426012af351a68463937ae2e934a2a1 + '') + (pkgs.writeScriptBin "wm-setup" '' + sqlx database create + wm-build + wm-caddy + wm-migrate + '') + (pkgs.writeScriptBin "wm-bench" '' + deno run -A benchmarks/main.ts -e admin@windmill.dev -p changeme "$@" + '') + ]; + + # --------------------------------------------------------------- + # Shared inputs and settings for default + full shells + # --------------------------------------------------------------- + + coreBuildInputs = nativeBuildDeps ++ commonRuntimes ++ [ + rustStable + openapi-generator-cli + ] ++ (with pkgs; [ + nodejs + git + sqlx-cli + cargo-watch + jq + gnused + + # CLI tools (for AI agents and dev workflow) + gh + asciinema + mermaid-cli + ]); + + # Playwright: use Nix-provided browsers (version-matched to playwright-driver) + # Mermaid/Puppeteer: point at Nix chromium (Puppeteer respects this env var) + browserVars = { + PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}"; + PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium"; + PUPPETEER_SKIP_DOWNLOAD = "true"; + }; + + # Wrapper for the Nix-provided playwright CLI (version-matched to its browsers) + playwrightWrapper = pkgs.writeShellScriptBin "playwright" '' + exec ${pkgs.nodejs}/bin/node ${pkgs.playwright-driver}/cli.js "$@" + ''; + in { - # Enter by `nix develop .#wasm` - devShells."wasm" = pkgs.mkShell { - # Explicitly set paths for headers and linker - shellHook = '' - export CC=${patchedClang}/bin/clang - ''; - buildInputs = buildInputs ++ (with pkgs; [ + + # ============================================================= + # default — daily driver for backend + frontend development + # Usage: nix develop + # ============================================================= + + devShells.default = pkgs.mkShell (buildEnvVars // commonRuntimeVars // devEnvVars // browserVars // { + buildInputs = coreBuildInputs; + + packages = helperScriptsBase ++ [ playwrightWrapper ]; + }); + + # ============================================================= + # full — all language runtimes, k8s tooling, specialized scripts + # Usage: nix develop .#full + # ============================================================= + + devShells.full = pkgs.mkShell (buildEnvVars // commonRuntimeVars // extraRuntimeVars // devEnvVars // browserVars // { + buildInputs = coreBuildInputs ++ extraRuntimes ++ (with pkgs; [ + # Python extras + poetry + pyright + openapi-python-client + + # LSP / editor + svelte-language-server + taplo + + # Extra dev tools + cargo-sweep + + # Kubernetes + minikube + kubectl + kubernetes-helm + conntrack-tools + cri-tools + + # Extra + xcaddy + nsjail + ]); + + packages = helperScriptsBase ++ helperScriptsFull ++ [ playwrightWrapper ]; + }); + + # ============================================================= + # wasm — WASM target compilation (nightly Rust) + # Usage: nix develop .#wasm + # ============================================================= + + devShells.wasm = pkgs.mkShell (buildEnvVars // { + hardeningDisable = [ "all" ]; + + buildInputs = nativeBuildDeps ++ (with pkgs; [ (rust-bin.nightly.latest.default.override { - extensions = [ - "rust-src" # for rust-analyzer - "rust-analyzer" - ]; - targets = - [ "wasm32-unknown-unknown" "wasm32-unknown-emscripten" ]; + extensions = [ "rust-src" "rust-analyzer" ]; + targets = [ "wasm32-unknown-unknown" "wasm32-unknown-emscripten" ]; }) wasm-pack deno emscripten nushell - # Needed for extra dependencies glibc_multi ]); - }; - devShells."cli" = pkgs.mkShell { + }); + + # ============================================================= + # cli — lightweight Bun-based CLI development + # Usage: nix develop .#cli + # ============================================================= + + devShells.cli = pkgs.mkShell { shellHook = '' if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then export FLAKE_ROOT="$(git rev-parse --show-toplevel)" else - # Fallback to PWD if not in a git repository export FLAKE_ROOT="$PWD" fi - wm-cli-deps ''; - buildInputs = buildInputs ++ [ pkgs.deno ]; + + buildInputs = with pkgs; [ bun nodejs git ]; + packages = [ (pkgs.writeScriptBin "wm-cli" '' - deno run -A --no-check $FLAKE_ROOT/cli/src/main.ts $* + bun run $FLAKE_ROOT/cli/src/main.ts "$@" '') (pkgs.writeScriptBin "wm-cli-deps" '' pushd $FLAKE_ROOT/cli/ - ${if pkgs.stdenv.isDarwin then + ${if stdenv.isDarwin then "./gen_wm_client_mac.sh && ./windmill-utils-internal/gen_wm_client_mac.sh" else "./gen_wm_client.sh && ./windmill-utils-internal/gen_wm_client.sh"} @@ -132,314 +443,5 @@ '') ]; }; - - devShells.default = pkgs.mkShell { - buildInputs = buildInputs ++ [ - # To update run: `nix flake update nixpkgs-oapi-gen` - openapi-generator-cli - ] ++ (with pkgs; [ - # Essentials - rust - git - sqlx-cli - - # Build/helper scripts - jq - gnused # other implementations are inconsistent on osx - - # Python - flock - python3 - python3Packages.pip - uv - poetry - pyright - openapi-python-client - - # Other languages - deno - typescript - nushell - go - bun - dotnet-sdk_9 - oracle-instantclient - ansible - ruby_3_4 - cargo-sweep # We use it for rust - - # LSP/Local dev - svelte-language-server - taplo - - # Orchestration/Kubernetes - minikube - kubectl - kubernetes-helm - conntrack-tools # To run minikube without driver (--driver=none) - cri-tools - - # Extra - xcaddy - cargo-watch - nsjail - sccache - ]); - packages = [ - (pkgs.writeScriptBin "wm-caddy" '' - cd ./frontend - xcaddy build $* \ - --with github.com/mholt/caddy-l4@145ec36251a44286f05a10d231d8bfb3a8192e09 \ - --with github.com/RussellLuo/caddy-ext/layer4@ab1e18cfe426012af351a68463937ae2e934a2a1 - '') - (pkgs.writeScriptBin "wm-build" '' - cd ./frontend - npm install - npm run ${ - if pkgs.stdenv.isDarwin then - "generate-backend-client-mac" - else - "generate-backend-client" - } - npm run build $* - '') - (pkgs.writeScriptBin "wm-migrate" '' - cd ./backend - sqlx migrate run - '') - (pkgs.writeScriptBin "wm-setup" '' - sqlx database create - wm-build - wm-caddy - wm-migrate - '') - (pkgs.writeScriptBin "wm-reset" '' - sqlx database drop -f - sqlx database create - wm-migrate - '') - (pkgs.writeScriptBin "wm-bench" '' - deno run -A benchmarks/main.ts -e admin@windmill.dev -p changeme $* - '') - (pkgs.writeScriptBin "wm" '' - cd ./frontend - npm install - npm run generate-backend-client - npm run dev $* - '') - (pkgs.writeScriptBin "wm-minio" '' - set -e - cd ./backend - mkdir -p .minio-data/wmill - ${pkgs.minio}/bin/minio server ./.minio-data --console-address ":9001" - '') - # Generate keys - # TODO: Do not set new keys if ran multiple times - (pkgs.writeScriptBin "wm-minio-keys" '' - set -e - cd ./backend - - # Set up MinIO alias - ${pkgs.minio-client}/bin/mc alias set 'wmill-minio-dev' 'http://localhost:9000' 'minioadmin' 'minioadmin' - - # Check if secrets file exists and contains valid keys - if [[ -f .minio-data/secrets.txt ]] && [[ -s .minio-data/secrets.txt ]]; then - echo "Access keys already exist:" - cat .minio-data/secrets.txt - echo "" - echo "Keys loaded from: ./backend/.minio-data/secrets.txt" - else - echo "Creating new access keys..." - mkdir -p .minio-data - ${pkgs.minio-client}/bin/mc admin accesskey create 'wmill-minio-dev' | tee .minio-data/secrets.txt - echo "" - echo 'New keys saved to: ./backend/.minio-data/secrets.txt' - fi - - echo "bucket: wmill" - echo "endpoint: http://localhost:9000" - '') - ]; - - inherit PKG_CONFIG_PATH RUSTY_V8_ARCHIVE; - GIT_PATH = "${pkgs.git}/bin/git"; - NODE_ENV = "development"; - NODE_OPTIONS = "--max-old-space-size=16384"; - # DATABASE_URL = "postgres://postgres:changeme@127.0.0.1:5432/"; - DATABASE_URL = - "postgres://postgres:changeme@127.0.0.1:5432/windmill?sslmode=disable"; - - REMOTE = "http://127.0.0.1:8000"; - REMOTE_LSP = "http://127.0.0.1:3001"; - # RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache"; - DENO_PATH = "${pkgs.deno}/bin/deno"; - GO_PATH = "${pkgs.go}/bin/go"; - PHP_PATH = "${pkgs.php}/bin/php"; - COMPOSER_PATH = "${pkgs.php84Packages.composer}/bin/composer"; - BUN_PATH = "${pkgs.bun}/bin/bun"; - NODE_PATH = "${pkgs.nodejs}/bin/node"; - NODE_BIN_PATH = "${pkgs.nodejs}/bin/node"; - UV_PATH = "${pkgs.uv}/bin/uv"; - NU_PATH = "${pkgs.nushell}/bin/nu"; - JAVA_PATH = "${pkgs.jdk21}/bin/java"; - JAVAC_PATH = "${pkgs.jdk21}/bin/javac"; - COURSIER_PATH = "${coursier}/coursier"; - BASH_PATH = "bash"; - RUBY_PATH = "${pkgs.ruby}/bin/ruby"; - RUBY_BUNDLE_PATH = "${pkgs.ruby}/bin/bundle"; - RUBY_GEM_PATH = "${pkgs.ruby}/bin/gem"; - # for related places search: ADD_NEW_LANG - FLOCK_PATH = "${pkgs.flock}/bin/flock"; - CARGO_PATH = "${rust}/bin/cargo"; - CARGO_SWEEP_PATH = "${pkgs.cargo-sweep}/bin/cargo-sweep"; - DOTNET_PATH = "${pkgs.dotnet-sdk_9}/bin/dotnet"; - DOTNET_ROOT = "${pkgs.dotnet-sdk_9}/share/dotnet"; - ORACLE_LIB_DIR = "${pkgs.oracle-instantclient.lib}/lib"; - ANSIBLE_PLAYBOOK_PATH = "${pkgs.ansible}/bin/ansible-playbook"; - ANSIBLE_GALAXY_PATH = "${pkgs.ansible}/bin/ansible-galaxy"; - # RUST_LOG = "debug"; - # RUST_LOG = "kube=debug"; - - # Override cargo linker to use clang 18 (stdenv brings clang 21 which causes SIGSEGV with mold) - CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER = "${pkgs.llvmPackages_18.clang}/bin/clang"; - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = "${pkgs.llvmPackages_18.clang}/bin/clang"; - CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS = "-C link-arg=-fuse-ld=mold -C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath [ pkgs.openssl pkgs.libffi pkgs.cyrus_sasl pkgs.krb5 pkgs.libxml2 pkgs.xmlsec pkgs.libxslt stdenv.cc.cc.lib ]}"; - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS = "-C link-arg=-fuse-ld=mold -C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath [ pkgs.openssl pkgs.libffi pkgs.cyrus_sasl pkgs.krb5 pkgs.libxml2 pkgs.xmlsec pkgs.libxslt stdenv.cc.cc.lib ]}"; - # rpath for build scripts and proc macros (host compilation) - CARGO_HOST_RUSTFLAGS = "-C link-arg=-Wl,-rpath,${pkgs.lib.makeLibraryPath [ pkgs.openssl pkgs.libffi pkgs.cyrus_sasl pkgs.krb5 pkgs.libxml2 pkgs.xmlsec pkgs.libxslt stdenv.cc.cc.lib ]}"; - - # See this issue: https://github.com/NixOS/nixpkgs/issues/370494 - # Allows to build jemalloc on nixos - CFLAGS = "-Wno-error=int-conversion"; - - # Need to tell bindgen where to find libclang - LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; - - # LD_LIBRARY_PATH set in shellHook with a wrapper to avoid leaking into git/ssh - # LD_LIBRARY_PATH = "${pkgs.gcc.lib}/lib"; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ - pkgs.zlib - ]; - - # Set C flags for Rust's bindgen program. Unlike ordinary C - # compilation, bindgen does not invoke $CC directly. Instead it - # uses LLVM's libclang. To make sure all necessary flags are - # included we need to look in a few places. - # See https://web.archive.org/web/20220523141208/https://hoverbear.org/blog/rust-bindgen-in-nix/ - BINDGEN_EXTRA_CLANG_ARGS = - # Prevent clang from using system headers - only use Nix headers - "-nostdinc ${ - builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags" - } ${builtins.readFile "${stdenv.cc}/nix-support/libc-cflags"} ${ - builtins.readFile "${stdenv.cc}/nix-support/cc-cflags" - } ${ - builtins.readFile "${stdenv.cc}/nix-support/libcxx-cxxflags" - } -idirafter ${pkgs.libiconv}/include ${ - lib.optionalString stdenv.cc.isClang - "-idirafter ${stdenv.cc.cc}/lib/clang/${ - lib.getVersion stdenv.cc.cc - }/include" - }${ - lib.optionalString stdenv.cc.isGNU - "-isystem ${stdenv.cc.cc}/include/c++/${ - lib.getVersion stdenv.cc.cc - } -isystem ${stdenv.cc.cc}/include/c++/${ - lib.getVersion stdenv.cc.cc - }/${stdenv.hostPlatform.config} -idirafter ${stdenv.cc.cc}/lib/gcc/${stdenv.hostPlatform.config}/${ - lib.getVersion stdenv.cc.cc - }/include" - }"; - }; - 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 ]; - doCheck = false; - - npmDepsHash = "sha256-NXk9mnf74+/k0i3goqU8Zi/jr5b/bmW+HWRLJCI2CX8="; - npmBuild = "npm run build"; - - postUnpack = '' - mkdir -p ./backend/windmill-api/ - cp ${ - ./backend/windmill-api/openapi.yaml - } ./backend/windmill-api/openapi.yaml - cp ${./openflow.openapi.yaml} ./openflow.openapi.yaml - ''; - preBuild = '' - npm run ${ - if pkgs.stdenv.isDarwin then - "generate-backend-client-mac" - else - "generate-backend-client" - } - ''; - - installPhase = '' - mkdir -p $out/build - cp -r build $out - ''; - - NODE_OPTIONS = "--max-old-space-size=8192"; - }; - packages.windmill = pkgs.rustPlatform.buildRustPackage { - pname = "windmill"; - version = (pkgs.lib.strings.trim (builtins.readFile ./version.txt)); - - src = ./backend; - nativeBuildInputs = buildInputs - ++ [ self.packages.${system}.windmill-client pkgs.perl ] - ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ - # Additional darwin specific inputs can be set here - pkgs.libiconv - pkgs.darwin.apple_sdk.frameworks.SystemConfiguration - ]; - - cargoLock = { - lockFile = ./backend/Cargo.lock; - outputHashes = { - "php-parser-rs-0.1.3" = - "sha256-ZeI3KgUPmtjlRfq6eAYveqt8Ay35gwj6B9iOQRjQa9A="; - "progenitor-0.3.0" = - "sha256-F6XRZFVIN6/HfcM8yI/PyNke45FL7jbcznIiqj22eIQ="; - "tinyvector-0.1.0" = - "sha256-NYGhofU4rh+2IAM+zwe04YQdXY8Aa4gTmn2V2HtzRfI="; - }; - }; - - buildFeatures = [ - "enterprise" - "enterprise_saml" - "stripe" - "embedding" - "parquet" - "prometheus" - "openidconnect" - "cloud" - "jemalloc" - "tantivy" - "license" - "http_trigger" - "zip" - "oauth2" - "kafka" - "otel" - "dind" - "websocket" - "smtp" - "static_frontend" - "all_languages" - ]; - doCheck = false; - - inherit PKG_CONFIG_PATH RUSTY_V8_ARCHIVE; - SQLX_OFFLINE = true; - FRONTEND_BUILD_DIR = - "${self.packages.${system}.windmill-client}/build"; - }; }); } diff --git a/sandbox-image/Dockerfile.sandbox b/sandbox-image/Dockerfile.sandbox index 072095c106..aaf71620d0 100644 --- a/sandbox-image/Dockerfile.sandbox +++ b/sandbox-image/Dockerfile.sandbox @@ -42,13 +42,6 @@ RUN bun add -g @playwright/test \ && bunx playwright install chromium --with-deps \ && rm -rf /var/lib/apt/lists/* /tmp/bunx-* -# ── AWS CLI ─────────────────────────────────────────────────────────────────── -RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \ - && unzip -q /tmp/awscliv2.zip -d /tmp \ - && /tmp/aws/install \ - && rm -rf /tmp/aws /tmp/awscliv2.zip -ENV AWS_DEFAULT_REGION=auto - # ── Claude Code ─────────────────────────────────────────────────────────────── RUN curl -fsSL https://claude.ai/install.sh | bash ENV PATH="/root/.local/bin:$PATH" From ff180de4de7d2e132ee0b7dd5e9071321dc790e1 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:00:19 +0100 Subject: [PATCH 05/35] refactor: slim down claude instructions for lean context and fast iteration (#8136) * refactor: slim down claude instructions for lean context and fast iteration Co-Authored-By: Claude Opus 4.6 * fix: add private and license feature flags to enterprise validation docs Co-Authored-By: Claude Opus 4.6 * feat: add /refine skill for end-of-session doc evolution Co-Authored-By: Claude Opus 4.6 * refactor: remove architecture.md overview doc per research findings General codebase overviews distract agents and trigger unnecessary exploration. Keep only operational docs (validation, enterprise). Co-Authored-By: Claude Opus 4.6 * feat: add autonomous mode doc for bypass permission workflows Covers: plan-first requirement, tmux pane usage for checking backend/frontend logs, manual testing via Playwright MCP, Playwright gotchas, and end-of-task summary expectations. Co-Authored-By: Claude Opus 4.6 * feat: add mermaid, playwright, and asciinema tools to autonomous mode doc Claude should use mmdc for diagrams during planning, playwright CLI for screenshots of frontend changes, and asciinema for terminal recordings of CLI changes. All attached to the PR. Co-Authored-By: Claude Opus 4.6 * fix: use pastebin for screenshot/recording uploads Co-Authored-By: Claude Opus 4.6 * fix: address PR review findings - Remove stale docs/architecture.md reference from /refine skill - Fix script name: ./update-sqlx -> ./update_sqlx.sh - Remove .claude/settings.local.json mention from enterprise doc Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .claude/skills/refine/SKILL.md | 39 ++ .claude/skills/rust-backend/SKILL.md | 486 +++--------------------- .claude/skills/svelte-frontend/SKILL.md | 324 +++------------- CLAUDE.md | 80 ++-- backend/CLAUDE.md | 104 +---- docs/autonomous-mode.md | 94 +++++ docs/enterprise.md | 38 ++ docs/validation.md | 55 +++ frontend/CLAUDE.md | 202 +--------- 9 files changed, 366 insertions(+), 1056 deletions(-) create mode 100644 .claude/skills/refine/SKILL.md create mode 100644 docs/autonomous-mode.md create mode 100644 docs/enterprise.md create mode 100644 docs/validation.md diff --git a/.claude/skills/refine/SKILL.md b/.claude/skills/refine/SKILL.md new file mode 100644 index 0000000000..aaf747cd29 --- /dev/null +++ b/.claude/skills/refine/SKILL.md @@ -0,0 +1,39 @@ +--- +name: refine +user_invocable: true +description: End-of-session reflection. Reviews friction encountered during the session and proposes updates to docs/ to capture lessons learned. +--- + +# Refine Skill + +Reflect on the current session and update documentation with lessons learned. + +## Instructions + +1. **Identify friction**: Review what happened in this session: + - Run `git diff main...HEAD --stat` to see what files were touched + - Think about: what was slow, what failed, what required multiple attempts, what information was missing or hard to find + +2. **Read current docs**: Read the docs that were relevant to this session: + - `docs/validation.md` + - `docs/enterprise.md` + - `docs/autonomous-mode.md` + - Any skills that were invoked + +3. **Propose updates**: For each piece of friction, decide if it warrants a doc update: + - **Missing knowledge**: Information you had to discover that should be documented + - **Wrong guidance**: Instructions that led you astray + - **Missing validation rule**: A check that should be in the validation matrix + - **New pattern**: A codebase pattern worth capturing for next time + +4. **Apply updates**: Edit the relevant `docs/` files. Keep changes minimal and specific — add only what would have saved time this session. + +5. **Report**: Summarize what was added/changed and why. + +## Rules + +- Only add knowledge confirmed by this session — no speculative additions +- Keep docs concise — add a line or two, not a paragraph +- If a whole new doc is needed, create it in `docs/` and add a pointer in `CLAUDE.md` +- Don't update skills unless a coding pattern was genuinely wrong +- Don't add things Claude already knows — only Windmill-specific knowledge diff --git a/.claude/skills/rust-backend/SKILL.md b/.claude/skills/rust-backend/SKILL.md index c2253319a6..f0c52002bc 100644 --- a/.claude/skills/rust-backend/SKILL.md +++ b/.claude/skills/rust-backend/SKILL.md @@ -3,493 +3,105 @@ name: rust-backend description: Rust coding guidelines for the Windmill backend. MUST use when writing or modifying Rust code in the backend directory. --- -# Rust Backend Coding Guidelines +# Windmill Rust Patterns -Apply these patterns when writing or modifying Rust code in the `backend/` directory. - -## Data Structure Design - -Choose between `struct`, `enum`, or `newtype` based on domain needs: - -- Use `enum` for state machines instead of boolean flags or loosely related fields -- Model invariants explicitly using types (e.g., `NonZeroU32`, `Duration`, custom enums) -- Consider ownership of each field: - - Use `&str` vs `String`, slices vs vectors - - Use `Arc` when sharing across threads - - Use `Cow<'a, T>` for flexible ownership - -```rust -// State machine with enum -enum JobState { - Pending { scheduled_for: DateTime }, - Running { started_at: DateTime, worker: String }, - Completed { result: JobResult, duration_ms: i64 }, - Failed { error: String, retries: u32 }, -} - -// Avoid multiple booleans -struct Job { - is_pending: bool, // Don't do this - is_running: bool, - is_completed: bool, -} -``` - -## Impl Block Organization - -Place `impl` blocks immediately below the struct/enum they modify. Group methods logically: - -```rust -struct JobQueue { - jobs: Vec, - capacity: usize, -} - -impl JobQueue { - // Constructors first - pub fn new(capacity: usize) -> Self { ... } - pub fn with_jobs(jobs: Vec) -> Self { ... } - - // Getters - pub fn len(&self) -> usize { ... } - pub fn is_empty(&self) -> bool { ... } - - // Mutation methods - pub fn push(&mut self, job: Job) -> Result<()> { ... } - pub fn pop(&mut self) -> Option { ... } - - // Domain logic - pub fn next_scheduled(&self) -> Option<&Job> { ... } -} -``` - -## Iterator Chains Over For-Loops - -Prefer functional iterator chains (`.filter().map().collect()`) over imperative for-loops: - -```rust -// Preferred -let results: Vec<_> = items - .iter() - .filter(|item| item.is_valid()) - .map(|item| item.transform()) - .collect(); - -// Avoid -let mut results = Vec::new(); -for item in items.iter() { - if item.is_valid() { - results.push(item.transform()); - } -} -``` +Apply these Windmill-specific patterns when writing Rust code in `backend/`. ## Error Handling -Use the `Error` type from `windmill_common::error`. Return `Result` or `JsonResult` for fallible functions: +Use `Error` from `windmill_common::error`. Return `Result` or `JsonResult`: ```rust use windmill_common::error::{Error, Result}; -// Use ? operator for propagation pub async fn get_job(db: &DB, id: Uuid) -> Result { - let job = sqlx::query_as!(Job, "SELECT ... WHERE id = $1", id) + sqlx::query_as!(Job, "SELECT id, workspace_id FROM v2_job WHERE id = $1", id) .fetch_optional(db) .await? .ok_or_else(|| Error::NotFound("job not found".to_string()))?; - Ok(job) } ``` -Prefer `if let` for optional handling. Use `let...else` when early return makes code clearer: +Never panic in library code. Reserve `.unwrap()` for compile-time guarantees. + +## SQLx Patterns + +**Never use `SELECT *`** — always list columns explicitly. Critical for backwards compatibility when workers lag behind API version: ```rust -let Some(config) = get_config() else { - return Err(Error::MissingConfig); -}; +// Correct +sqlx::query_as!(Job, "SELECT id, workspace_id, path FROM v2_job WHERE id = $1", id) + +// Wrong — breaks when columns are added +sqlx::query_as!(Job, "SELECT * FROM v2_job WHERE id = $1", id) ``` -Never panic in library code. Reserve `.unwrap()` for cases with compile-time guarantees. Keep functions short to help lifetime inference and clarity. - -## Early Returns - -Return early to avoid deep nesting. Handle error cases and edge conditions first: +Use batch operations to avoid N+1: ```rust -// Preferred - early returns -fn process_job(job: Option) -> Result { - let Some(job) = job else { - return Ok(Output::default()); - }; - - if !job.is_valid() { - return Err(Error::InvalidJob); - } - - if job.is_cached() { - return Ok(job.cached_result()); - } - - // Main logic at the end, not nested - execute_job(job) -} - -// Avoid - deep nesting -fn process_job(job: Option) -> Result { - if let Some(job) = job { - if job.is_valid() { - if !job.is_cached() { - execute_job(job) - } else { - Ok(job.cached_result()) - } - } else { - Err(Error::InvalidJob) - } - } else { - Ok(Output::default()) - } -} +// Preferred — single query with IN clause +sqlx::query!("SELECT ... WHERE id = ANY($1)", &ids[..]).fetch_all(db).await? ``` -## Variable Shadowing - -Shadow variables instead of creating new names with prefixes: - -```rust -// Preferred -let data = fetch_raw_data(); -let data = parse(data); -let data = validate(data)?; - -// Avoid -let raw_data = fetch_raw_data(); -let parsed_data = parse(raw_data); -let validated_data = validate(parsed_data)?; -``` - -## Minimal Comments - -- No inline comments explaining obvious code -- No TODO/FIXME comments in committed code -- Doc comments (`///`) only on public items -- Let code be self-documenting through clear naming - -## Type Safety - -Use enums over boolean flags for clarity: - -```rust -// Preferred -enum JobStatus { - Pending, - Running, - Completed, -} - -// Avoid -struct Job { - is_running: bool, - is_completed: bool, -} -``` - -## Pattern Matching - -Prefer explicit matching. Use wildcards strategically for fallback cases or ignored fields: - -```rust -// Explicit matching preferred -match status { - JobStatus::Pending => handle_pending(), - JobStatus::Running => handle_running(), - JobStatus::Completed => handle_completed(), -} - -// Wildcards OK for fallback -match result { - Ok(value) => process(value), - Err(_) => return default_value(), -} - -// Wildcards OK for ignoring fields in destructuring -let Point { x, y, .. } = point; -``` - -## Destructuring in Function Signatures - -Destructure structs directly in function parameters: - -```rust -// Preferred -async fn process_job( - Extension(db): Extension, - Path((workspace, job_id)): Path<(String, Uuid)>, - Query(pagination): Query, -) -> Result> { - // ... -} - -// Avoid -async fn process_job( - db_ext: Extension, - path: Path<(String, Uuid)>, - query: Query, -) -> Result> { - let Extension(db) = db_ext; - let Path((workspace, job_id)) = path; - // ... -} -``` - -## Trait Implementations - -Use standard trait implementations to simplify conversions and reduce boilerplate: - -```rust -// Implement From/Into for type conversions -impl From for ApiJob { - fn from(db: DbJob) -> Self { - ApiJob { - id: db.id, - status: db.status.into(), - } - } -} - -// Use TryFrom for fallible conversions -impl TryFrom for JobKind { - type Error = Error; - fn try_from(s: String) -> Result { ... } -} -``` - -Apply `derive` macros to reduce boilerplate: - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Job { ... } -``` - -## Module Structure - -- Use `pub(crate)` instead of `pub` when possible; expose only what needs exposing -- Keep APIs small and expressive; avoid leaking internal types -- Organize code into modules reflecting ownership and domain boundaries - -```rust -// Prefer restricted visibility -pub(crate) fn internal_helper() { ... } - -// Only pub for external API -pub fn create_job(...) -> Result { ... } -``` - -## Code Navigation - -Always use rust-analyzer LSP for: -- Go to definition -- Find references -- Type information -- Import resolution - -Do not guess at module paths or type definitions. +Use transactions for multi-step operations. Parameterize all queries. ## JSON Handling -Prefer `Box` over `serde_json::Value` when: -- Storing JSON in the database (JSONB columns) -- Passing JSON through without modification -- The JSON structure doesn't need inspection +Prefer `Box` over `serde_json::Value` when storing/passing JSON without inspection: ```rust -// Preferred - avoids parsing/serialization overhead pub struct Job { - pub id: Uuid, pub args: Option>, } - -// Only use Value when you need to inspect/modify JSON -let value: serde_json::Value = serde_json::from_str(&json)?; -if let Some(field) = value.get("field") { - // modify or inspect -} ``` -## Serde Optimizations +Only use `serde_json::Value` when you need to inspect or modify the JSON. -Use serde attributes to optimize serialization: +## Serde Optimizations ```rust #[derive(Serialize, Deserialize)] pub struct Job { - #[serde(rename = "jobId")] - pub id: Uuid, - - #[serde(default)] - pub priority: i32, - #[serde(skip_serializing_if = "Option::is_none")] pub parent_job: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] pub tags: Vec, + #[serde(default)] + pub priority: i32, } ``` -Prefer borrowing for zero-copy deserialization when lifetimes allow: +## Async & Concurrency + +Never block the async runtime. Use `spawn_blocking` for CPU-intensive work: ```rust -#[derive(Deserialize)] -pub struct JobInput<'a> { - #[serde(borrow)] - pub workspace_id: Cow<'a, str>, - - #[serde(borrow)] - pub script_path: &'a str, -} +let result = tokio::task::spawn_blocking(move || expensive_computation(&data)).await?; ``` -## SQLx Patterns +**Mutex selection**: Prefer `std::sync::Mutex` (or `parking_lot::Mutex`) for data protection. Only use `tokio::sync::Mutex` when holding locks across `.await` points. -**Never use `SELECT *`** - always list columns explicitly. This is critical for backwards compatibility when workers run behind the API server version: +Use `tokio::sync::mpsc` (bounded) for channels. Avoid `std::thread::sleep` in async contexts. + +## Module Structure & Visibility + +- Use `pub(crate)` instead of `pub` when possible +- Place new code in the appropriate crate based on functionality +- API endpoints go in `windmill-api/src/` organized by domain +- Shared functionality goes in `windmill-common/src/` + +## Code Navigation + +Always use rust-analyzer LSP for go-to-definition, find-references, and type info. Do not guess at module paths. + +## Axum Handlers + +Destructure extractors directly in function signatures: ```rust -// Preferred - explicit columns -sqlx::query_as!( - Job, - "SELECT id, workspace_id, path, created_at FROM v2_job WHERE id = $1", - job_id -) - -// Avoid - breaks when columns are added -sqlx::query_as!(Job, "SELECT * FROM v2_job WHERE id = $1", job_id) +async fn process_job( + Extension(db): Extension, + Path((workspace, job_id)): Path<(String, Uuid)>, + Query(pagination): Query, +) -> Result> { ... } ``` - -Use batch operations to minimize round trips: - -```rust -// Preferred - single query with multiple values -sqlx::query!( - "INSERT INTO job_logs (job_id, logs) VALUES ($1, $2), ($3, $4)", - id1, log1, id2, log2 -) - -// Avoid N+1 queries -for id in ids { - sqlx::query!("SELECT ... WHERE id = $1", id).fetch_one(db).await?; -} - -// Preferred - single query with IN clause -sqlx::query!("SELECT ... WHERE id = ANY($1)", &ids[..]).fetch_all(db).await? -``` - -Use transactions for multi-step operations and parameterize all queries. - -## Async & Tokio Patterns - -Never block the async runtime. Use `spawn_blocking` for CPU-intensive or blocking I/O: - -```rust -// Preferred - offload blocking work -let result = tokio::task::spawn_blocking(move || { - expensive_computation(&data) -}).await?; - -// Avoid - blocks the runtime -let result = expensive_computation(&data); // Don't do this in async -``` - -Use tokio primitives for sleep and channels: - -```rust -use tokio::sync::mpsc; -use tokio::time::sleep; - -// Avoid in async contexts -use std::thread::sleep; // Blocks the runtime -``` - -Use bounded channels for backpressure: - -```rust -// Preferred - bounded channel prevents overwhelming -let (tx, rx) = tokio::sync::mpsc::channel(100); - -// Be careful with unbounded -let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); -``` - -## Mutex Selection in Async Code - -**Prefer `std::sync::Mutex` (or `parking_lot::Mutex`) over `tokio::sync::Mutex`** for protecting data in async code. The async mutex is more expensive and only needed when holding locks across `.await` points. - -```rust -// Preferred for data protection - std mutex is faster -use std::sync::Mutex; - -struct Cache { - data: Mutex>, -} - -impl Cache { - fn get(&self, key: &str) -> Option { - self.data.lock().unwrap().get(key).cloned() - } - - fn insert(&self, key: String, value: Value) { - self.data.lock().unwrap().insert(key, value); - } -} -``` - -**Use `tokio::sync::Mutex` only when you must hold the lock across `.await` points**, typically for IO resources like database connections: - -```rust -use tokio::sync::Mutex; -use std::sync::Arc; - -// Async mutex for IO resources held across await points -let conn = Arc::new(Mutex::new(db_connection)); - -async fn execute_query(conn: Arc>, query: &str) { - let mut lock = conn.lock().await; - lock.execute(query).await; // Lock held across .await -} -``` - -**Common pattern**: Wrap `Arc>` in a struct with non-async methods that lock internally, keeping lock scope minimal: - -```rust -struct SharedState { - inner: std::sync::Mutex, -} - -impl SharedState { - fn update(&self, value: i32) { - self.inner.lock().unwrap().value = value; - } - - fn get(&self) -> i32 { - self.inner.lock().unwrap().value - } -} -``` - -**Alternative for IO resources**: Spawn a dedicated task to manage the resource and communicate via message passing: - -```rust -let (tx, mut rx) = tokio::sync::mpsc::channel(32); - -tokio::spawn(async move { - while let Some(cmd) = rx.recv().await { - handle_io_command(&mut resource, cmd).await; - } -}); -``` - -## Build & Tooling - -Build speed tips: -- Use `cargo check` during rapid iteration over `cargo build` -- Minimize unnecessary dependencies and feature flags \ No newline at end of file diff --git a/.claude/skills/svelte-frontend/SKILL.md b/.claude/skills/svelte-frontend/SKILL.md index f51d36672a..57cac70302 100644 --- a/.claude/skills/svelte-frontend/SKILL.md +++ b/.claude/skills/svelte-frontend/SKILL.md @@ -3,316 +3,78 @@ name: svelte-frontend description: Svelte coding guidelines for the Windmill frontend. MUST use when writing or modifying code in the frontend directory. --- -# Svelte 5 Best Practices +# Windmill Svelte Patterns -This guide outlines best practices for developing with Svelte 5, incorporating the new Runes API and other modern Svelte features. These rules MUST NOT be applied on svelte 4 files unless explicitly asked to do so. +Apply these Windmill-specific patterns when writing Svelte code in `frontend/`. For general Svelte 5 syntax (runes, snippets, event handling), use the Svelte MCP server. -## Reactivity with Runes +## Windmill UI Components (MUST use) -Svelte 5 introduces Runes for more explicit and flexible reactivity. +Always use Windmill's design-system components. Never use raw HTML elements. -1. **Embrace Runes for State Management**: - * Use `$state` for reactive local component state. - ```svelte - - - - ``` - * Use `$derived` for computed values based on other reactive state. - ```svelte - - -

{count} * 2 = {doubled}

- ``` - * Use `$effect` for side effects that need to run when reactive values change (e.g., logging, manual DOM manipulation, data fetching). Remember `$effect` does not run on the server. - ```svelte - - ``` - -2. **Props with `$props`**: - * Declare component props using `$props()`. This offers better clarity and flexibility compared to `export let`. - ```svelte - - -

Name: {name}

-

Age: {age}

- ``` - * For bindable props, use `$bindable`. - ```svelte - - - - ``` - -## Event Handling - -* **Use direct event attributes**: Svelte 5 moves away from `on:` directives for DOM events. - * **Do**: `` - * **Don't**: `` -* **For component events, prefer callback props**: Instead of `createEventDispatcher`, pass functions as props. - ```svelte - - - -

Message from child: {message}

- - - - - ``` - -## Snippets for Content Projection - -* **Use `{#snippet ...}` and `{@render ...}` instead of slots**: Snippets are more powerful and flexible. - ```svelte - - - - - {#snippet title()} - My Awesome Title - {/snippet} - {#snippet content()} -

Some interesting content here.

- {/snippet} -
- - - - -
-
{@render title()}
-
{@render content()}
-
- ``` -* Default content is passed via the `children` prop (which is a snippet). - ```svelte - - -
- {@render children?.()} -
- ``` - -## Component Design - -1. **Create Small, Reusable Components**: Break down complex UIs into smaller, focused components. Each component should have a single responsibility. This also aids performance by limiting the scope of reactivity updates. -2. **Descriptive Naming**: Use clear and descriptive names for variables, functions, and components. -3. **Minimize Logic in Components**: Move complex business logic to utility functions or services. Keep components focused on presentation and interaction. - -## State Management (Stores) - -1. **Segment Stores**: Avoid a single global store. Create multiple stores, each responsible for a specific piece of global state (e.g., `userStore.js`, `themeStore.js`). This can help limit reactivity updates to only the parts of the UI that depend on specific state segments. -2. **Use Custom Stores for Complex Logic**: For stores with related methods, create custom stores. - ```javascript - // counterStore.js - import { writable } from 'svelte/store'; - - function createCounter() { - const { subscribe, set, update } = writable(0); - - return { - subscribe, - increment: () => update(n => n + 1), - decrement: () => update(n => n - 1), - reset: () => set(0) - }; - } - export const counter = createCounter(); - ``` -3. **Use Context API for Localized State**: For state shared within a component subtree, consider Svelte's context API (`setContext`, `getContext`) instead of global stores when the state doesn't need to be truly global. - -## Performance Optimizations (Svelte 5) - -When generating Svelte 5 code, prioritize frontend performance by applying the following principles: - -### General Svelte 5 Principles - -- **Leverage the Compiler:** Trust Svelte's compiler to generate optimized JavaScript. Avoid manual DOM manipulation (`document.querySelector`, etc.) unless absolutely necessary for integrating third-party libraries that lack Svelte adapters. -- **Keep Components Small and Focused:** Reinforcing from Component Design, smaller components lead to less complex reactivity graphs and more targeted, efficient updates. - -### Reactivity & State Management - -- **Optimize Computations with `$derived`:** Always use `$derived` for computed values that depend on other state. This ensures the computation only runs when its specific dependencies change, avoiding unnecessary work compared to recomputing derived values in `$effect` or less efficient methods. -- **Minimize `$effect` Usage:** Use `$effect` sparingly and only for true side effects that interact with the outside world or non-Svelte state. Avoid putting complex logic or state updates *within* an `$effect` unless those updates are explicitly intended as a reaction to external changes or non-Svelte state. Excessive or complex effects can impact rendering performance. -- **Structure State for Fine-Grained Updates:** Design your `$state` objects or variables such that updates affect only the necessary parts of the UI. Avoid putting too much unrelated state into a single large object that gets frequently updated, as this can potentially trigger broader updates than necessary. Consider normalizing complex, nested state. - -### List Rendering (`{#each}`) - -- **Mandate `key` Attribute:** Always use a `key` attribute (`{#each items as item (item.id)}`) that refers to a unique, stable identifier for each item in a list. This is critical for allowing Svelte to efficiently update, reorder, add, or remove list items without destroying and re-creating unnecessary DOM elements and component instances. - -### Component Loading & Bundling - -- **Implement Lazy Loading/Code Splitting:** For routes, components, or modules that are not immediately needed on page load, use dynamic imports (`import(...)`) to split the code bundle. SvelteKit handles this automatically for routes, but it can be applied manually to components using helper patterns if needed. -- **Be Mindful of Third-Party Libraries:** When incorporating external libraries, import only the necessary functions or components to minimize the final bundle size. Prefer libraries designed to be tree-shakeable. - -### Rendering & DOM - -- **Use CSS for Animations/Transitions:** Prefer CSS animations or transitions where possible for performance. Svelte's built-in `transition:` directive is also highly optimized and should be used for complex state-driven transitions, but simple cases can often use plain CSS. -- **Optimize Image Loading:** Implement best practices for images: use optimized formats (WebP, AVIF), lazy loading (`loading="lazy"`), and responsive images (``, `srcset`) to avoid loading unnecessarily large images. - -### Server-Side Rendering (SSR) & Hydration - -- **Ensure SSR Compatibility:** Write components that can be rendered on the server for faster initial page loads. Avoid relying on browser-specific APIs (like `window` or `document`) in the main ` - -
- Hello -
- ``` -5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes. - -## Windmill UI Component Rules (MUST follow) - -Always use Windmill's own design-system components instead of raw HTML elements. Using raw HTML elements produces inconsistent styling and breaks the design language. - -### Icons — use `lucide-svelte` - -**Never** write inline SVGs. Import icons from `lucide-svelte`. - -```svelte - - - -``` - -### Buttons — use ` - - - @@ -821,6 +821,23 @@
+ {#if batchProgress} +
+ Loading jobs: {batchProgress.loaded} of {batchProgress.total}... +
+
+
+ +
+ {/if}
{#if jobs} @@ -831,6 +848,7 @@ showExternalJobs={graph !== 'RunChart'} activeLabel={filters.val.label} {lastFetchWentToEnd} + loadingExtra={jobsLoader.loadingExtra} bind:selectedIds bind:selectedWorkspace on:loadExtra={loadExtra} diff --git a/frontend/src/lib/components/runs/RunsTable.svelte b/frontend/src/lib/components/runs/RunsTable.svelte index a9a7823fc8..b6c38bd380 100644 --- a/frontend/src/lib/components/runs/RunsTable.svelte +++ b/frontend/src/lib/components/runs/RunsTable.svelte @@ -37,6 +37,7 @@ batchRerunOptionsIsOpen?: boolean manualSelectionMode: undefined | 'cancel' | 'rerun' onCancelJobs: (jobIds: string[]) => void + loadingExtra?: boolean } let { @@ -51,7 +52,8 @@ perPage = 1000, manualSelectionMode, onCancelJobs, - batchRerunOptionsIsOpen = $bindable() + batchRerunOptionsIsOpen = $bindable(), + loadingExtra = false }: Props = $props() let hasClickFocus = $state(false) @@ -535,11 +537,16 @@ >{#if !lastFetchWentToEnd && jobs && jobs.length >= perPage} {/if}
diff --git a/frontend/src/lib/components/runs/useJobsLoader.svelte.ts b/frontend/src/lib/components/runs/useJobsLoader.svelte.ts index e2bbeb3fe0..f87119d7dc 100644 --- a/frontend/src/lib/components/runs/useJobsLoader.svelte.ts +++ b/frontend/src/lib/components/runs/useJobsLoader.svelte.ts @@ -58,7 +58,6 @@ export interface UseJobLoaderArgs { skip?: boolean lookback?: number perPage?: number - onSetPerPage?: (perPage: number) => void } export function useJobsLoader(args: () => UseJobLoaderArgs) { @@ -73,7 +72,6 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { let refreshRate = $derived(_args.refreshRate ?? 5000) let syncQueuedRunsCount = $derived(_args.syncQueuedRunsCount ?? true) let lookback = $derived(_args.lookback ?? 0) - let onSetPerPage = $derived(_args.onSetPerPage) let timeframe = $derived(_args?.timeframe) let perPage = $derived(_args?.perPage ?? 1000) @@ -100,7 +98,9 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { let queue_count: Tweened | undefined = $state() let suspended_count: Tweened | undefined = $state() let loading = $state(false) + let loadingExtra = $state(false) let lastFetchWentToEnd = $state(true) + let batchProgress = $state<{ loaded: number; total: number } | null>(null) let completedJobs: CompletedJob[] | undefined = $state() let externalJobs: Job[] | undefined = $state() @@ -115,15 +115,13 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { function onParamChanges() { resetJobs() let promise = loadJobsIntern(true) - promise = CancelablePromiseUtils.onTimeout(promise, 4000, () => { - sendUserToast( - 'Loading jobs is taking longer than expected...', - 'warning', - perPage > 25 && onSetPerPage - ? [{ label: 'Reduce to 25 items per page', callback: () => onSetPerPage(25) }] - : [] - ) - }) + if (perPage > 25) { + promise = CancelablePromiseUtils.onTimeout(promise, 4000, () => { + sendUserToast('Loading jobs is taking longer than expected...', 'warning', [ + { label: 'Stream by batches of 25', callback: () => restreamWithSmallBatches() } + ]) + }) + } promise = CancelablePromiseUtils.catchErr(promise, (e) => { if (e instanceof CancelError) { return CancelablePromiseUtils.pure(undefined) @@ -133,50 +131,62 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { return promise } + function restreamWithSmallBatches() { + paramChangePromise?.cancel() + resetJobs() + paramChangePromise = loadJobsIntern(false, 25) + paramChangePromise = CancelablePromiseUtils.catchErr(paramChangePromise, (e) => { + if (e instanceof CancelError) { + return CancelablePromiseUtils.pure(undefined) + } + return CancelablePromiseUtils.err(e) + }) + } + let loadingFetch = false async function loadExtraJobs(): Promise { - if (jobs && jobs.length > 0) { - let minQueueTs: string | undefined = undefined - let minCompletedTs: string | undefined = undefined + loading = true + loadingExtra = true + const batchSize = Math.min(perPage, 1000) + await loadExtraJobsBatch(batchSize) + loadingExtra = false + } - let cursor = 0 - - while (jobs && cursor < jobs?.length) { - cursor++ - const job = jobs[jobs.length - 1 - cursor] - if (job.type == 'CompletedJob') { - minCompletedTs = job.completed_at - break - } else if (job.type == 'QueuedJob' && minQueueTs == undefined) { - minQueueTs = job.created_at - } - } - - const ts = minCompletedTs ?? minQueueTs - - if (!ts) { - sendUserToast('No jobs to load from') - lastFetchWentToEnd = false - return - } - // const minCreated = lastJob?.created_at - const minCreated = new Date(new Date(ts).getTime() - 1).toISOString() - - const minTs = timeframe?.computeMinMax().minTs ?? null - let olderJobs = await fetchJobs(minCreated, minTs, undefined) - jobs = updateWithNewJobs(olderJobs ?? [], jobs ?? []) - computeCompletedJobs() - lastFetchWentToEnd = olderJobs?.length < perPage - } else { - lastFetchWentToEnd = false + function loadExtraJobsBatch(batchSize: number): CancelablePromise { + if (!jobs || jobs.length === 0) { + lastFetchWentToEnd = true + return CancelablePromiseUtils.pure(undefined as void) } + const lastJob = jobs[jobs.length - 1] + const ts = lastJob.created_at + if (!ts) { + lastFetchWentToEnd = true + return CancelablePromiseUtils.pure(undefined as void) + } + const cursorTs = new Date(new Date(ts).getTime() - 1).toISOString() + const minTs = timeframe?.computeMinMax().minTs ?? null + return CancelablePromiseUtils.map( + fetchJobs(null, minTs, undefined, cursorTs, batchSize), + (olderJobs) => { + jobs = updateWithNewJobs(olderJobs ?? [], jobs ?? []) + if (extendedJobs) { + extendedJobs.jobs = jobs ?? [] + extendedJobs = extendedJobs + } + computeCompletedJobs() + lastFetchWentToEnd = (olderJobs?.length ?? 0) < batchSize + loading = false + } + ) } function fetchJobs( completedBefore: string | null, completedAfter: string | null, - createdAfterQueue: string | undefined + createdAfterQueue: string | undefined, + createdBefore?: string, + perPageOverride?: number ): CancelablePromise { if (_args.skip) return CancelablePromiseUtils.pure([]) loadingFetch = true @@ -186,6 +196,7 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { let isCompletedOnly = success == 'success' || success == 'failure' let promise = JobService.listJobs({ workspace: currentWorkspace, + createdBefore: createdBefore ?? undefined, completedBefore: isQueueOnly ? undefined : (completedBefore ?? undefined), completedAfter: isQueueOnly ? undefined : (completedAfter ?? undefined), createdBeforeQueue: isQueueOnly ? (completedBefore ?? undefined) : undefined, @@ -226,7 +237,7 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { : undefined, triggerKind: jobTriggerKind ?? undefined, allWorkspaces: allWorkspaces ? true : undefined, - perPage, + perPage: perPageOverride ?? perPage, allowWildcards: allowWildcards ? true : undefined, broadFilter }) @@ -299,16 +310,26 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { await loadJobsIntern(shouldGetCount) } + function stopBatchLoading(): void { + paramChangePromise?.cancel() + batchProgress = null + loading = false + } + function resetJobs() { jobs = undefined completedJobs = undefined externalJobs = undefined extendedJobs = undefined lastFetchWentToEnd = false + batchProgress = null intervalId && clearInterval(intervalId) intervalId = setInterval(syncer, refreshRate) } - function loadJobsIntern(shouldGetCount?: boolean): CancelablePromise { + function loadJobsIntern( + shouldGetCount?: boolean, + overrideBatchSize?: number + ): CancelablePromise { const { minTs, maxTs } = timeframe?.computeMinMax() ?? { minTs: null, maxTs: null } if (shouldGetCount) { getCount() @@ -320,8 +341,13 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { const extendedMinTs = subtractDaysFromDateString(minTs, lookback) if (concurrencyKey == null || concurrencyKey === '') { - return CancelablePromiseUtils.map( - fetchJobs(maxTs, extendedMinTs ?? null, extendedMinTs), + const batchSize = overrideBatchSize ?? Math.min(perPage, 1000) + const isBatched = perPage > batchSize + if (isBatched) { + batchProgress = { loaded: 0, total: perPage } + } + let p = CancelablePromiseUtils.map( + fetchJobs(maxTs, extendedMinTs ?? null, extendedMinTs, undefined, batchSize), (newJobs) => { extendedJobs = { jobs: newJobs, obscured_jobs: [] } as ExtendedJobs @@ -330,10 +356,30 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { jobs = sortMinDate(minTs, newJobs) externalJobs = [] computeCompletedJobs() - lastFetchWentToEnd = newJobs.length < perPage + lastFetchWentToEnd = newJobs.length < batchSize loading = false + if (isBatched) { + batchProgress = { loaded: jobs?.length ?? 0, total: perPage } + } } ) + if (isBatched) { + const numExtraBatches = Math.ceil(perPage / batchSize) - 1 + for (let i = 0; i < numExtraBatches; i++) { + p = CancelablePromiseUtils.then(p, (): CancelablePromise => { + if (lastFetchWentToEnd || !jobs) + return CancelablePromiseUtils.pure(undefined as void) + loading = true + return CancelablePromiseUtils.pipe(loadExtraJobsBatch(batchSize), () => { + batchProgress = { loaded: jobs?.length ?? 0, total: perPage } + }) + }) + } + } + p = CancelablePromiseUtils.pipe(p, () => { + batchProgress = null + }) + return p } else { return CancelablePromiseUtils.map( fetchExtendedJobs(concurrencyKey, maxTs, extendedMinTs ?? null), @@ -577,6 +623,10 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { return { loadExtraJobs, loadJobs, + stopBatchLoading, + get batchProgress() { + return batchProgress + }, get queue_count() { return queue_count }, @@ -586,6 +636,9 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { get loading() { return loading }, + get loadingExtra() { + return loadingExtra + }, get completedJobs() { return completedJobs }, From 7a32abec96124f96a1dbac11e03162cca68f3286 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 1 Mar 2026 06:39:24 +0000 Subject: [PATCH 27/35] feat: slow stream warnings, batch size control, and fix result/skipped filters (#8154) - Show recurring toast every 15s (8s duration) when loading takes long, with stop button - When streaming by batches of 25 and a batch takes >4s, offer to stream 1 by 1 - Expose batch size in progress bar with editable input to customize on the fly - Make stop button more prominent (destructive Button component) - Fix list_jobs UNION: exclude queue jobs when filtering by result or is_skipped=true - Add "Show skipped" preset to runs filter Co-authored-by: Claude Opus 4.6 --- backend/windmill-api/src/jobs.rs | 2 + frontend/src/lib/components/RunsPage.svelte | 26 +++- .../src/lib/components/runs/runsFilter.ts | 1 + .../components/runs/useJobsLoader.svelte.ts | 124 +++++++++++++++--- 4 files changed, 130 insertions(+), 23 deletions(-) diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 2528e2556f..420bbf24b9 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -2062,6 +2062,8 @@ async fn list_jobs( let sql = if lq.success.is_none() && lq.label.is_none() + && lq.result.is_none() + && !lq.is_skipped.unwrap_or(false) && lq.created_before.is_none() && lq.started_before.is_none() && lq.created_or_started_before.is_none() diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte index 81727a08c9..59b63ab313 100644 --- a/frontend/src/lib/components/RunsPage.svelte +++ b/frontend/src/lib/components/RunsPage.svelte @@ -171,6 +171,7 @@ currentWorkspace: $workspaceStore ?? '' })) let batchProgress = $derived(jobsLoader.batchProgress) + let currentBatchSize = $derived(jobsLoader.currentBatchSize) let lastFetchWentToEnd = $derived(jobsLoader.lastFetchWentToEnd) let queue_count = $derived(jobsLoader.queue_count) let suspended_count = $derived(jobsLoader.suspended_count) @@ -830,12 +831,29 @@ style="width: {Math.round((batchProgress.loaded / batchProgress.total) * 100)}%" >
- + {/if} diff --git a/frontend/src/lib/components/runs/runsFilter.ts b/frontend/src/lib/components/runs/runsFilter.ts index 589c44a984..3aaa9a2842 100644 --- a/frontend/src/lib/components/runs/runsFilter.ts +++ b/frontend/src/lib/components/runs/runsFilter.ts @@ -238,6 +238,7 @@ export const buildRunsFilterPresets = ({ }) => [ { name: 'Hide schedules', value: 'job_trigger_kind:\\ !schedule' }, { name: 'Hide future jobs', value: 'show_future_jobs:\\ false' }, + { name: 'Show skipped', value: 'show_skipped:\\ true' }, ...(isSuperadmin && isAdminsWorkspace ? [{ name: 'All workspaces', value: 'all_workspaces:\\ true' }] : []) diff --git a/frontend/src/lib/components/runs/useJobsLoader.svelte.ts b/frontend/src/lib/components/runs/useJobsLoader.svelte.ts index f87119d7dc..5e9aa29222 100644 --- a/frontend/src/lib/components/runs/useJobsLoader.svelte.ts +++ b/frontend/src/lib/components/runs/useJobsLoader.svelte.ts @@ -111,9 +111,20 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { let sync = true let paramChangeTimeout: ReturnType | undefined let paramChangePromise: CancelablePromise | undefined + let slowStreamIntervalId: ReturnType | undefined + let currentBatchSize = $state(null) function onParamChanges() { resetJobs() + slowStreamIntervalId = setInterval(() => { + sendUserToast( + 'Loading is taking a long time...', + 'warning', + [{ label: 'Stop loading', callback: () => stopBatchLoading() }], + undefined, + 8000 + ) + }, 15000) let promise = loadJobsIntern(true) if (perPage > 25) { promise = CancelablePromiseUtils.onTimeout(promise, 4000, () => { @@ -122,6 +133,12 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { ]) }) } + promise = CancelablePromiseUtils.pipe(promise, () => { + if (slowStreamIntervalId) { + clearInterval(slowStreamIntervalId) + slowStreamIntervalId = undefined + } + }) promise = CancelablePromiseUtils.catchErr(promise, (e) => { if (e instanceof CancelError) { return CancelablePromiseUtils.pure(undefined) @@ -131,10 +148,25 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { return promise } - function restreamWithSmallBatches() { + function restreamWithBatchSize(size: number) { paramChangePromise?.cancel() resetJobs() - paramChangePromise = loadJobsIntern(false, 25) + slowStreamIntervalId = setInterval(() => { + sendUserToast( + 'Loading is taking a long time...', + 'warning', + [{ label: 'Stop loading', callback: () => stopBatchLoading() }], + undefined, + 8000 + ) + }, 15000) + paramChangePromise = loadJobsIntern(false, size) + paramChangePromise = CancelablePromiseUtils.pipe(paramChangePromise, () => { + if (slowStreamIntervalId) { + clearInterval(slowStreamIntervalId) + slowStreamIntervalId = undefined + } + }) paramChangePromise = CancelablePromiseUtils.catchErr(paramChangePromise, (e) => { if (e instanceof CancelError) { return CancelablePromiseUtils.pure(undefined) @@ -143,6 +175,14 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { }) } + function restreamWithSmallBatches() { + restreamWithBatchSize(25) + } + + function restreamWithSingleItems() { + restreamWithBatchSize(1) + } + let loadingFetch = false async function loadExtraJobs(): Promise { @@ -312,7 +352,12 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { function stopBatchLoading(): void { paramChangePromise?.cancel() + if (slowStreamIntervalId) { + clearInterval(slowStreamIntervalId) + slowStreamIntervalId = undefined + } batchProgress = null + currentBatchSize = null loading = false } @@ -323,6 +368,11 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { extendedJobs = undefined lastFetchWentToEnd = false batchProgress = null + currentBatchSize = null + if (slowStreamIntervalId) { + clearInterval(slowStreamIntervalId) + slowStreamIntervalId = undefined + } intervalId && clearInterval(intervalId) intervalId = setInterval(syncer, refreshRate) } @@ -346,23 +396,41 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { if (isBatched) { batchProgress = { loaded: 0, total: perPage } } - let p = CancelablePromiseUtils.map( - fetchJobs(maxTs, extendedMinTs ?? null, extendedMinTs, undefined, batchSize), - (newJobs) => { - extendedJobs = { jobs: newJobs, obscured_jobs: [] } as ExtendedJobs - - // Filter on minTs here and not in the backend - // to get enough data for the concurrency graph - jobs = sortMinDate(minTs, newJobs) - externalJobs = [] - computeCompletedJobs() - lastFetchWentToEnd = newJobs.length < batchSize - loading = false - if (isBatched) { - batchProgress = { loaded: jobs?.length ?? 0, total: perPage } - } - } + currentBatchSize = batchSize + let slowBatchToastShown = false + let firstFetchPromise = fetchJobs( + maxTs, + extendedMinTs ?? null, + extendedMinTs, + undefined, + batchSize ) + if (isBatched && batchSize > 1) { + firstFetchPromise = CancelablePromiseUtils.onTimeout(firstFetchPromise, 4000, () => { + if (!slowBatchToastShown) { + slowBatchToastShown = true + sendUserToast( + `Streaming by batches of ${batchSize} is slow, try loading one at a time`, + 'warning', + [{ label: 'Stream 1 by 1', callback: () => restreamWithSingleItems() }] + ) + } + }) + } + let p = CancelablePromiseUtils.map(firstFetchPromise, (newJobs) => { + extendedJobs = { jobs: newJobs, obscured_jobs: [] } as ExtendedJobs + + // Filter on minTs here and not in the backend + // to get enough data for the concurrency graph + jobs = sortMinDate(minTs, newJobs) + externalJobs = [] + computeCompletedJobs() + lastFetchWentToEnd = newJobs.length < batchSize + loading = false + if (isBatched) { + batchProgress = { loaded: jobs?.length ?? 0, total: perPage } + } + }) if (isBatched) { const numExtraBatches = Math.ceil(perPage / batchSize) - 1 for (let i = 0; i < numExtraBatches; i++) { @@ -370,14 +438,28 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { if (lastFetchWentToEnd || !jobs) return CancelablePromiseUtils.pure(undefined as void) loading = true - return CancelablePromiseUtils.pipe(loadExtraJobsBatch(batchSize), () => { + let batchPromise = CancelablePromiseUtils.pipe(loadExtraJobsBatch(batchSize), () => { batchProgress = { loaded: jobs?.length ?? 0, total: perPage } }) + if (batchSize > 1 && !slowBatchToastShown) { + batchPromise = CancelablePromiseUtils.onTimeout(batchPromise, 4000, () => { + if (!slowBatchToastShown) { + slowBatchToastShown = true + sendUserToast( + `Streaming by batches of ${batchSize} is slow, try loading one at a time`, + 'warning', + [{ label: 'Stream 1 by 1', callback: () => restreamWithSingleItems() }] + ) + } + }) + } + return batchPromise }) } } p = CancelablePromiseUtils.pipe(p, () => { batchProgress = null + currentBatchSize = null }) return p } else { @@ -624,9 +706,13 @@ export function useJobsLoader(args: () => UseJobLoaderArgs) { loadExtraJobs, loadJobs, stopBatchLoading, + restreamWithBatchSize, get batchProgress() { return batchProgress }, + get currentBatchSize() { + return currentBatchSize + }, get queue_count() { return queue_count }, From 7d6f4fdabb912ec757f39e940fd5e6e1ab4d9653 Mon Sep 17 00:00:00 2001 From: Fred Reimer Date: Sun, 1 Mar 2026 01:53:33 -0500 Subject: [PATCH 28/35] chore: bump Go in container images to 1.26.0 (#8135) --- Dockerfile | 2 +- backend/windmill-common/src/workspace_dependencies.rs | 2 +- docker/DockerfileExtra | 6 +++--- docker/entrypoint-extra.sh | 2 +- .../src/lib/components/WorkspaceDependenciesEditor.svelte | 2 +- lsp/Dockerfile | 6 +++--- lsp/pyls_launcher.py | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 32d0087244..1c5b8a9834 100644 --- a/Dockerfile +++ b/Dockerfile @@ -126,7 +126,7 @@ ARG POWERSHELL_DEB_VERSION=7.5.0-1 ARG KUBECTL_VERSION=1.28.7 ARG HELM_VERSION=3.14.3 # NOTE: If changing, also change go version in workspace dependencies template at WorkspaceDependenciesEditor.svelte -ARG GO_VERSION=1.25.0 +ARG GO_VERSION=1.26.0 ARG APP=/usr/src/app ARG WITH_POWERSHELL=true ARG WITH_KUBECTL=true diff --git a/backend/windmill-common/src/workspace_dependencies.rs b/backend/windmill-common/src/workspace_dependencies.rs index 2ec3e26e37..cf38003057 100644 --- a/backend/windmill-common/src/workspace_dependencies.rs +++ b/backend/windmill-common/src/workspace_dependencies.rs @@ -510,7 +510,7 @@ impl WorkspaceDependenciesPrefetched { // external.get(0).map(|wd| dbg!(wd.content.clone())).or(Some( // " // module mymod - // go 1.25 + // go 1.26 // require () // " // .to_owned(), diff --git a/docker/DockerfileExtra b/docker/DockerfileExtra index 79a1645217..68b4a282e2 100644 --- a/docker/DockerfileExtra +++ b/docker/DockerfileExtra @@ -44,9 +44,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN set -eux; \ arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \ case "$arch" in \ - 'amd64') targz='go1.21.13.linux-amd64.tar.gz' ;; \ - 'arm64') targz='go1.21.13.linux-arm64.tar.gz' ;; \ - 'armhf') targz='go1.21.13.linux-armv6l.tar.gz' ;; \ + 'amd64') targz='go1.26.0.linux-amd64.tar.gz' ;; \ + 'arm64') targz='go1.26.0.linux-arm64.tar.gz' ;; \ + 'armhf') targz='go1.26.0.linux-armv6l.tar.gz' ;; \ *) echo >&2 "error: unsupported architecture '$arch'"; exit 1 ;; \ esac; \ wget "https://golang.org/dl/$targz" -nv && tar -C /usr/local -xzf "$targz" && rm "$targz" diff --git a/docker/entrypoint-extra.sh b/docker/entrypoint-extra.sh index 141a148771..eedd169a31 100644 --- a/docker/entrypoint-extra.sh +++ b/docker/entrypoint-extra.sh @@ -36,7 +36,7 @@ fi # Setup Monaco temp directory for LSP mkdir -p /tmp/monaco if [ ! -f /tmp/monaco/go.mod ]; then - echo -e "module mymod\ngo 1.25" > /tmp/monaco/go.mod + echo -e "module mymod\ngo 1.26" > /tmp/monaco/go.mod fi echo "[entrypoint] Starting Windmill Extra Services" diff --git a/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte b/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte index b643c7e86e..ec0b342f56 100644 --- a/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte +++ b/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte @@ -138,7 +138,7 @@ numpy>=1.24.0 // go: `module mymod - // go 1.25 + // go 1.26 // require ( // rsc.io/quote v1.5.2 diff --git a/lsp/Dockerfile b/lsp/Dockerfile index 075ad9dba0..7efa5e8cd9 100644 --- a/lsp/Dockerfile +++ b/lsp/Dockerfile @@ -21,13 +21,13 @@ RUN set -eux; \ url=; \ case "$arch" in \ 'amd64') \ - targz='go1.25.0.linux-amd64.tar.gz'; \ + targz='go1.26.0.linux-amd64.tar.gz'; \ ;; \ 'arm64') \ - targz='go1.25.0.linux-arm64.tar.gz'; \ + targz='go1.26.0.linux-arm64.tar.gz'; \ ;; \ 'armhf') \ - targz='go1.25.0.linux-armv6l.tar.gz'; \ + targz='go1.26.0.linux-armv6l.tar.gz'; \ ;; \ *) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \ esac; \ diff --git a/lsp/pyls_launcher.py b/lsp/pyls_launcher.py index e41c3a3bd3..c77a08c35c 100644 --- a/lsp/pyls_launcher.py +++ b/lsp/pyls_launcher.py @@ -97,7 +97,7 @@ if __name__ == "__main__": go_mod_path = os.path.join(monaco_path, "go.mod") if not os.path.exists(go_mod_path): f = open(go_mod_path, "w") - f.write("module mymod\ngo 1.25") + f.write("module mymod\ngo 1.26") f.close() port = int(os.environ.get("PORT", "3001")) app = web.Application( From 36353359f63204271f4aa0cd5e9a172331eedb25 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 1 Mar 2026 07:00:37 +0000 Subject: [PATCH 29/35] chore(main): release 1.647.0 (#8127) * chore(main): release 1.647.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 31 +++ backend/Cargo.lock | 181 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 142 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eee595bd2..8276e8d8d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## [1.647.0](https://github.com/windmill-labs/windmill/compare/v1.646.0...v1.647.0) (2026-03-01) + + +### Features + +* populate baseUrl and userId in Nextcloud resource from OAuth ([#8132](https://github.com/windmill-labs/windmill/issues/8132)) ([5d58a87](https://github.com/windmill-labs/windmill/commit/5d58a87a7f02c4f7775bd02c885071495a5f686d)) +* runScript inline for path and hash ([#8019](https://github.com/windmill-labs/windmill/issues/8019)) ([7d9d16a](https://github.com/windmill-labs/windmill/commit/7d9d16a6a3357981e5692023982ca1e670acfaae)) +* slow stream warnings, batch size control, and fix result/skipped filters ([#8154](https://github.com/windmill-labs/windmill/issues/8154)) ([7a32abe](https://github.com/windmill-labs/windmill/commit/7a32abec96124f96a1dbac11e03162cca68f3286)) + + +### Bug Fixes + +* : persist show schedules and show future jobs toggles in local storage ([#8125](https://github.com/windmill-labs/windmill/issues/8125)) ([f1d8568](https://github.com/windmill-labs/windmill/commit/f1d8568831bf69ee790def4f90df8f32c59a94e0)), closes [#8123](https://github.com/windmill-labs/windmill/issues/8123) +* add partial index for fast failure filtering on runs page ([#8150](https://github.com/windmill-labs/windmill/issues/8150)) ([d4673c2](https://github.com/windmill-labs/windmill/commit/d4673c2e91168dcdb0aca9d6c039df0d9c52bb28)) +* copy deps and remove user auto-add on workspace fork ([#8142](https://github.com/windmill-labs/windmill/issues/8142)) ([0776de6](https://github.com/windmill-labs/windmill/commit/0776de6b2173075f533fd59a49efb111000da5df)) +* fix custom TS Monaco worker not reloading on file uri change ([#8130](https://github.com/windmill-labs/windmill/issues/8130)) ([b68ff96](https://github.com/windmill-labs/windmill/commit/b68ff965dd4f67046fae7e8cf756c8b3e15c2643)) +* Handle CTEs and local tables in SQL asset parser ([#8131](https://github.com/windmill-labs/windmill/issues/8131)) ([0955051](https://github.com/windmill-labs/windmill/commit/095505136c2b3e03f656ace20a5c1bbe142fa63f)) +* prevent wm-cursor from hanging on stale cursor IPC sockets ([b9e3e05](https://github.com/windmill-labs/windmill/commit/b9e3e053e4914e753bbb806e6b748c791edb92d2)) +* process deletes before adds in CLI sync push to avoid conflicts ([#8148](https://github.com/windmill-labs/windmill/issues/8148)) ([278983c](https://github.com/windmill-labs/windmill/commit/278983c4fd38d67a14a8c208178c04db05ee1880)) +* remove review comments from discord notifications and support comment edits ([cdc0543](https://github.com/windmill-labs/windmill/commit/cdc0543747680267e30974037a2eb180a19062d9)) +* restore email domain (MX) setting in instance settings UI ([#8152](https://github.com/windmill-labs/windmill/issues/8152)) ([13daebf](https://github.com/windmill-labs/windmill/commit/13daebf88ac1abcb833646490073f922ac7c050e)) +* sync flow on_behalf_of_email on load ([#8149](https://github.com/windmill-labs/windmill/issues/8149)) ([faf190f](https://github.com/windmill-labs/windmill/commit/faf190f12d96cd75ba9eda10ab3e6f26d2eed813)) +* validate tarball URL host against registry to prevent SSRF and token exfiltration ([#8153](https://github.com/windmill-labs/windmill/issues/8153)) ([86182ed](https://github.com/windmill-labs/windmill/commit/86182ed2e999f018fc72343308e7df8e9de6c189)) + + +### Performance Improvements + +* batch large job list requests and fix loadExtraJobs cursor ([#8151](https://github.com/windmill-labs/windmill/issues/8151)) ([4f5a804](https://github.com/windmill-labs/windmill/commit/4f5a8040912e18f34401a6e3a95dea6f97d1d24c)) +* lazy-load heavy deps (graphql, openapi-parser, sha256) ([#8145](https://github.com/windmill-labs/windmill/issues/8145)) ([ba48d70](https://github.com/windmill-labs/windmill/commit/ba48d7015741eb6bbbe04088a957c37499cd8471)) +* lazy-load markdown in Tooltip components ([#8143](https://github.com/windmill-labs/windmill/issues/8143)) ([bd9ff03](https://github.com/windmill-labs/windmill/commit/bd9ff03010f75557dcc315d10e9208b4e9cafece)) + ## [1.646.0](https://github.com/windmill-labs/windmill/compare/v1.645.0...v1.646.0) (2026-02-26) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index dcfede37e2..8d4a5aa431 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -8049,13 +8049,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ "bitflags 2.9.4", "libc", - "redox_syscall 0.7.2", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -10085,18 +10086,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -10105,9 +10106,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -10159,6 +10160,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "png" version = "0.17.16" @@ -10855,9 +10862,9 @@ dependencies = [ [[package]] name = "range-alloc" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" [[package]] name = "raw-cpuid" @@ -10989,9 +10996,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ "bitflags 2.9.4", ] @@ -15725,7 +15732,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-nats", @@ -15789,7 +15796,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15802,7 +15809,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "argon2", @@ -15940,7 +15947,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15963,7 +15970,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15976,7 +15983,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16002,7 +16009,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.646.0" +version = "1.647.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -16012,7 +16019,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16029,7 +16036,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16052,7 +16059,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16075,7 +16082,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16091,7 +16098,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16111,7 +16118,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16131,7 +16138,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16145,7 +16152,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-nats", @@ -16172,7 +16179,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16197,7 +16204,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "flate2", @@ -16215,7 +16222,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16236,7 +16243,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16256,7 +16263,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16286,7 +16293,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16313,7 +16320,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.646.0" +version = "1.647.0" dependencies = [ "lazy_static", "serde", @@ -16325,7 +16332,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.646.0" +version = "1.647.0" dependencies = [ "argon2", "axum 0.7.9", @@ -16348,7 +16355,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16362,7 +16369,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.646.0" +version = "1.647.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16392,7 +16399,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.646.0" +version = "1.647.0" dependencies = [ "chrono", "lazy_static", @@ -16406,7 +16413,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16425,7 +16432,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.646.0" +version = "1.647.0" dependencies = [ "aes-gcm", "anyhow", @@ -16524,7 +16531,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.646.0" +version = "1.647.0" dependencies = [ "chrono", "itertools 0.14.0", @@ -16543,7 +16550,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.646.0" +version = "1.647.0" dependencies = [ "regex", "serde", @@ -16558,7 +16565,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16582,7 +16589,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "futures", @@ -16599,7 +16606,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.646.0" +version = "1.647.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16615,7 +16622,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -16636,7 +16643,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -16667,7 +16674,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-oauth2", @@ -16691,7 +16698,7 @@ dependencies = [ [[package]] name = "windmill-object-store" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-stream", @@ -16725,7 +16732,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "futures", @@ -16743,7 +16750,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.646.0" +version = "1.647.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16752,7 +16759,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "lazy_static", @@ -16764,7 +16771,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "serde_json", @@ -16776,7 +16783,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "gosyn", @@ -16788,7 +16795,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "lazy_static", @@ -16800,7 +16807,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "serde_json", @@ -16812,7 +16819,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "nu-parser", @@ -16823,7 +16830,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16834,7 +16841,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16847,7 +16854,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-recursion", @@ -16871,7 +16878,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "lazy_static", @@ -16885,7 +16892,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16902,7 +16909,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "lazy_static", @@ -16917,7 +16924,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "lazy_static", @@ -16936,7 +16943,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "serde", @@ -16947,7 +16954,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-recursion", @@ -16984,7 +16991,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "const_format", @@ -17022,7 +17029,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.646.0" +version = "1.647.0" dependencies = [ "getrandom 0.3.4", "wasm-bindgen", @@ -17033,7 +17040,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-recursion", @@ -17062,7 +17069,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -17085,7 +17092,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17118,7 +17125,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17138,7 +17145,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17172,7 +17179,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17207,7 +17214,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17230,7 +17237,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17254,7 +17261,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-nats", @@ -17278,7 +17285,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17313,7 +17320,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17341,7 +17348,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-trait", @@ -17364,7 +17371,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "bitflags 2.9.4", @@ -17382,7 +17389,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.646.0" +version = "1.647.0" dependencies = [ "anyhow", "async-once-cell", @@ -18352,18 +18359,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index cea4408bd6..f67a635efc 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.646.0" +version = "1.647.0" authors.workspace = true edition.workspace = true @@ -76,7 +76,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.646.0" +version = "1.647.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index dbf058e4cb..df7a38b7d0 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.646.0 + version: 1.647.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index e45c27b96f..f19c2c98e6 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.646.0"; +export const VERSION = "v1.647.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 06ef57bb3e..489666f1e6 100755 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -67,7 +67,7 @@ export { workspaceAdd, }; -export const VERSION = "1.646.0"; +export const VERSION = "1.647.0"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3bfd393db1..e49c34127e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.646.0", + "version": "1.647.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.646.0", + "version": "1.647.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 0e77dd5dff..580b6867d5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.646.0", + "version": "1.647.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 188a8d5685..5e96167e73 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.646.0" -wmill_pg = ">=1.646.0" +wmill = ">=1.647.0" +wmill_pg = ">=1.647.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index a33bf589ea..3286540995 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.646.0 + version: 1.647.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index c2346ef452..06af5f0da6 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.646.0' + ModuleVersion = '1.647.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index c7f4217d4b..bcbfde941d 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.646.0" +version = "1.647.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 747396e026..eae37b843d 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.646.0" +version = "1.647.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 3307e3f9a9..275e75a398 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.646.0", + "version": "1.647.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 44e77802ec..2cc6be7679 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.646.0", + "version": "1.647.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index 0cdcc52abb..32f772cdb8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.646.0 +1.647.0 From 6eca08480a1cc3ca58d58f502a64e616b7958918 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 1 Mar 2026 08:52:35 +0000 Subject: [PATCH 30/35] chore: remove legacy wmill_pg python client (#8155) The wmill_pg package (psycopg2 wrapper for running PostgreSQL queries) has been fully replaced by Windmill's native PostgreSQL support. Remove the package directory and all references from build, publish, install, version, LSP, and dependabot configs. Co-authored-by: Claude Opus 4.6 --- .github/change-versions-mac.sh | 3 -- .github/change-versions.sh | 3 -- .github/dependabot.yml | 6 --- lsp/Pipfile | 1 - python-client/build.sh | 1 - python-client/install.sh | 3 -- python-client/publish.sh | 2 - python-client/wmill_pg/README.md | 17 ------- .../dist/wmill_pg-1.245.0-py3-none-any.whl | Bin 2299 -> 0 bytes .../wmill_pg/dist/wmill_pg-1.245.0.tar.gz | Bin 1933 -> 0 bytes python-client/wmill_pg/pyproject.toml | 48 ------------------ python-client/wmill_pg/wmill_pg/__init__.py | 1 - python-client/wmill_pg/wmill_pg/client.py | 40 --------------- python-client/wmill_pg/wmill_pg/py.typed | 1 - 14 files changed, 126 deletions(-) delete mode 100644 python-client/wmill_pg/README.md delete mode 100644 python-client/wmill_pg/dist/wmill_pg-1.245.0-py3-none-any.whl delete mode 100644 python-client/wmill_pg/dist/wmill_pg-1.245.0.tar.gz delete mode 100644 python-client/wmill_pg/pyproject.toml delete mode 100644 python-client/wmill_pg/wmill_pg/__init__.py delete mode 100644 python-client/wmill_pg/wmill_pg/client.py delete mode 100644 python-client/wmill_pg/wmill_pg/py.typed diff --git a/.github/change-versions-mac.sh b/.github/change-versions-mac.sh index b12d83b530..d4d709f86f 100755 --- a/.github/change-versions-mac.sh +++ b/.github/change-versions-mac.sh @@ -15,11 +15,8 @@ sed -i '' -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/typescrip sed -i '' -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/package.json sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml sed -i '' -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml -sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml sed -i '' -e "/^[[:space:]]*ModuleVersion[[:space:]]*=/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1 -# sed -i '' -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml sed -i '' -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile -sed -i '' -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile sed -i '' -E "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" ${root_dirpath}/backend/Cargo.lock diff --git a/.github/change-versions.sh b/.github/change-versions.sh index 7db47d014c..759d8f793f 100755 --- a/.github/change-versions.sh +++ b/.github/change-versions.sh @@ -16,11 +16,8 @@ sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/typescript-c sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" ${root_dirpath}/frontend/package.json sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml sed -i -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill/pyproject.toml -sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml sed -i -e "/^[[:space:]]*ModuleVersion[[:space:]]*=/s/= .*/= '$VERSION'/" ${root_dirpath}/powershell-client/WindmillClient/WindmillClient.psd1 -# sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" ${root_dirpath}/python-client/wmill_pg/pyproject.toml sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile -sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" ${root_dirpath}/lsp/Pipfile sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" ${root_dirpath}/backend/Cargo.lock diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9559a252a4..80970d1e76 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -31,9 +31,3 @@ updates: directory: "/python-client/wmill" schedule: interval: "weekly" - - # Maintain dependencies for wmill_pg python client - - package-ecosystem: "pip" - directory: "/python-client/wmill_pg" - schedule: - interval: "weekly" diff --git a/lsp/Pipfile b/lsp/Pipfile index 5e96167e73..6ece442b78 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -5,7 +5,6 @@ name = "pypi" [packages] wmill = ">=1.647.0" -wmill_pg = ">=1.647.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/python-client/build.sh b/python-client/build.sh index 7abcd92a09..825d0f3933 100755 --- a/python-client/build.sh +++ b/python-client/build.sh @@ -61,7 +61,6 @@ mv windmill-api/README.md.tmp windmill-api/README.md cd windmill-api && poetry build cd ../wmill && poetry build -cd ../wmill_pg && poetry build cd .. && echo "windmill-api/" >> .gitignore # Build documentation (similar to typescript-client/build_typedoc.sh) diff --git a/python-client/install.sh b/python-client/install.sh index ae463574fb..6201928d0f 100755 --- a/python-client/install.sh +++ b/python-client/install.sh @@ -3,14 +3,11 @@ set -e rm windmill-api/dist/* || true rm wmill/dist/* || true -rm wmill_pg/dist/* || true ./build.sh pip3 install windmill-api/dist/*.whl --force-reinstall --user --break-system-packages pip3 install wmill/dist/*.whl --force-reinstall --user --break-system-packages -pip3 install wmill_pg/dist/*.whl --force-reinstall --user --break-system-packages rm -rf windmill-api/ rm -rf wmill/dist/* -rm -rf install wmill_pg/dist/* diff --git a/python-client/publish.sh b/python-client/publish.sh index b6610ebf7f..6b1d1b3d5d 100755 --- a/python-client/publish.sh +++ b/python-client/publish.sh @@ -5,7 +5,5 @@ set -e cd windmill-api && poetry publish --username __token__ --password $PYPI_PASSWORD -n || true cd ../wmill && poetry publish --username __token__ --password $PYPI_PASSWORD -n || true -sleep 5 -cd ../wmill_pg && poetry publish --username __token__ --password $PYPI_PASSWORD -n || true rm -rf windmill-api/ diff --git a/python-client/wmill_pg/README.md b/python-client/wmill_pg/README.md deleted file mode 100644 index 4e043806be..0000000000 --- a/python-client/wmill_pg/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# wmill - -The postgres extension client for the [Windmill](https://windmill.dev) platform. - -[windmill-api](https://pypi.org/project/windmill-api/). - -## Quickstart - -```python -import wmill_pg - - -def main(): - my_list = query("UPDATE demo SET value = 'value' RETURNING key, value") - for key, value in my_list: - ... -``` diff --git a/python-client/wmill_pg/dist/wmill_pg-1.245.0-py3-none-any.whl b/python-client/wmill_pg/dist/wmill_pg-1.245.0-py3-none-any.whl deleted file mode 100644 index 5e487f5b880b7c1781082e243b5079c7d6695e78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2299 zcmaKt2{e>@AIE28?6NO~!th>8#xS-b8BB&5VT^3ACd#sxAu*Cv<8%L10(vgxA)6-yo)`@%f4QqjeANwRsMwGg#YxS_e zsQQnd^m0NI-u0}fa2$BcGb?sOw3B6K?C+2j5+QQjYVs)d^q|s7T~^q^2}MGX)7YDJ zi2bzAjQeJ&2+5yIL2X^@h*1cyx2x$X*zUiob6BuR?f$h7zdLFqB;*1$5R1iJ=0XE6 zj!C$MeMoXFETg+)i5hQZlx4kA1|M6XY#8P%Cd9tAtTK<>hq@w7{1-ggRD-yJV3P!a z0od?Z>nObo!BvyZ(Ec?^FlDxWLI2;K`Kshc@tY>ilx;d#GVE01CX(i&l*MQtvOZdB z`gYd0ctpJ6XM<1?V3L1YvUpS0^Rz4JjiD3fr!^PuMz(t>Ao8jX=v{*cN9@`yWOsBu z_<^#p;xz!4i#P2RLid`YY(Y2u**7#4K>}cIw!Mub+`3?QiZ4FrC%W2Z(wql zw1R!euj*U_m|Z{CCYLvn-deqBtm*f@@t5faK9(7$ z_7%)|Hfa-&jvIzpG{mrxsevtamRjinHBMae1sipD#SIJ%!T5`VFYP8X(^}cRs^_NU z7LlE_H-ojY3@)+|C!QnI( z@tn!JGBq^Sv&BD(^n8+!|( z#=TX!Y2_R#O~xvGt{ED2Ldc#!7cr@^u#;>9i&mpNGs(I6oZKMTeGI6JZUE6|hlW+~`2--j@mPXHw4cG+z^Dr~Ac$MHbqU>1+#0eal zm_KLKKN4DBlXi(lsp?=qoi^S=r>NHPYyqE+Cdtwlo0}nFLe(#)@CNny(O096jp;`-B1@fmKSlFlSdWI!zM8u0yrQ!%_iZkH zOPn&DI3aT3Nb&n&az-K$Xki*$fUGDAqBj<;a!DYPTLdkC`KkCxdl7f;bqAR@ro*f} zZP9mRs^ao8{Ue^oB1SrVUE*RHG5NtuMzBOzjfz{c^0n^KQV@Q_t~*>snuk*_!N;0Z@c-!BP+u&~2e#?Y-p6U+c+FB^S;R}Tu~GeAsXH8peKX+tawl&*u0 zICIqSc4?M5Jp8Ng#$KqFiA!ovU zso=jua=(55DEw;wb&vf!gK;2y8iT&p^Ebdhn`0mCf0F!#2JHJj?XO(hqd+{L QEj*mNg3}BJyr19x0O#3~j{pDw diff --git a/python-client/wmill_pg/dist/wmill_pg-1.245.0.tar.gz b/python-client/wmill_pg/dist/wmill_pg-1.245.0.tar.gz deleted file mode 100644 index e539cc5f8882bec7eeb2e5362bc2390890191493..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1933 zcmV;82XgoyiwFn+00002|95R^Y;0d}XDu-@wsZGEr&Ayjk`VJPAZVNC(|^AONb1FLdgH0%>s|1CkVpW# zSS%LY#R8;5I^Unto1YmCnArO&a7ujX-*LLV&9f?Z9nbal@Xg*6AkQQf(EZ-G32z@q zl1J>o9qjk_ok4ftk>N|n8FpWyJ%j&yTt_?%r|H6WiP!5Br*n3Ec=Ya=M8V^(|3R1_>)#!E?jCl``v2;9hP4GCbg^VOO)|LOjQxl&M;eA7NHQce=F=%ga~ z$b8w^Zq{i5;j?G>x19TzYGx!tA3l6Y3%N{U#G^D364y415DM5FN0i6S)(BxhR7^vj zNqm4m;37`;l(iySd`%N;|qL6!<<2NLq9a|+405uvy(R`KjBMOw99I%rFp3_ zZwfJwYwl%N3WN|P-v98IlKd(BzxTQScZ~mk68}G_{r}ME_q?Io?U9#Vx9b`2|84qz z_&k^2Wg?R(duA_QAlfB}quJ;H;FiaJm{WL55Bwcv+jK@lwQ1t=^IiBS_4D77PB#S06 zgikparLmTru#k!uskA@x_#;*2x6d)3;{YNp8vD@l z06X~k>h>1TD45A=1@$b1r|q)8z~h=?wIcB9CsD*@)jke@Qt=D|+)}FPj#Thbp7T;|T5{`={4+86ACvzv`5zPi z)$!ljUrnbx=5jhEY4MfwKaO`d{{zpSiT|DgbCE=tQ~_F*UwL6x{x<{T|Be4Q{@?ij z&;5Vt6aKjO|BwFP^BniC|96H1eNGZ@KPSJ=-&~} zq-evIJC2#J(lI00-b(=+-hlgZtSf=<#L`)S4h_Rjeb{8Ru>Bfa>R@Fo^E6~*XxLUq zE0YP(pCmD}$`cpMvh@FNsz*#W$5jE9RObQmOpT)!bDeO?WAJS;qjE{`Ib)bhpxH?t zseDzG2zAOcPYBZ89EwG@IqIo~sgB7$mHlUG%+OyOUvs%ka;dWn(0}8*#+nSoOLF0} zk}?Gma5C4avARaT7b(MydOQuKM@hL#r0Av_wN~=sEhxS)8#E7PeNvU-#;%SV1iz80 zd{!uCS1@kHOm30ZO?P+)GbJ633%uY!gs&5EnWfZcm5R=o%tgFW(lIE42|Z^jg~N59 z6vxn)&`s5dV8KbIRF6UF6zlUkr2{qhdLlLrlwvE&a%>sdu>{ozOnI61X>QciR#Vg3 z$dxXpzRu-ndnPKsQ>%ZAs-ocMwPXh zAj8D}CjK|^zrmj^{;$ug9%udkZvL<9-HHFZ!(QLS|4)Hu_#G9O5VPsiB^7<09-rd= zz%{G?+g$&rzx-ssIr;I|NA~}}xBqv&{vH4C_6Fwu=ab+alT>Y=?Z1}i-6QM~7oE@u zcC7U+5VWqVj0BzMQAE{t_>bNFKtCrDv(+V!5x(#426|gw4S{~v7emmYzRfYh+c!C? zLYqaDha>#?wGMPv-G{JG_5F$wetpm-!}hBlaQq>pS;ps_0oSTWJR0F&&)-15sYtFs z-cc_XZ%6oOJCD=8-;9nnyIuQ- zBnEqx_65W?BP_vsJ7CI}L2?bdZ*v>^F>XVf(80jKz`(%3z`(%3z`(%3z`(%3z`(%3 Tz`(%Zzk`1RcvLNx08jt`z<%UW diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml deleted file mode 100644 index eae37b843d..0000000000 --- a/python-client/wmill_pg/pyproject.toml +++ /dev/null @@ -1,48 +0,0 @@ -[tool.poetry] -name = "wmill-pg" -version = "1.647.0" -description = "An extension client for the wmill client library focused on pg" -license = "Apache-2.0" -homepage = "https://windmill.dev" -documentation = "https://windmill.dev" - -authors = ["Ruben Fiszel "] - -readme = "README.md" -packages = [ - {include = "wmill_pg"}, -] -include = ["wmill_pg/py.typed"] - -[tool.poetry.dependencies] -python = "^3.7" -wmill = "^1.5.0" -psycopg2-binary = "*" - -[build-system] -requires = ["poetry>=1.0.2", "poetry-dynamic-versioning"] -build-backend = "poetry.masonry.api" - -[tool.poetry-dynamic-versioning] -enable = true -vcs = "git" -style = "semver" -format-jinja = "{% if distance == 0 %}{{ base }}{% else %}{{ base }}+{{ distance }}.{{ commit }}{% endif %}" - - -[tool.black] -line-length = 120 -target_version = ['py36', 'py37', 'py38'] -exclude = ''' -( - /( - | \.git - | \.venv - | \.mypy_cache - )/ -) -''' - -[tool.isort] -line_length = 120 -profile = "black" diff --git a/python-client/wmill_pg/wmill_pg/__init__.py b/python-client/wmill_pg/wmill_pg/__init__.py deleted file mode 100644 index 421945bc42..0000000000 --- a/python-client/wmill_pg/wmill_pg/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .client import * diff --git a/python-client/wmill_pg/wmill_pg/client.py b/python-client/wmill_pg/wmill_pg/client.py deleted file mode 100644 index 73b5efc3fe..0000000000 --- a/python-client/wmill_pg/wmill_pg/client.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Any - -import wmill - -import psycopg2 - - -def query(query: str, connection: "str | dict[str, Any]" = "g/all/postgres") -> "list[tuple[Any, ...]] | None": - """ - Query a postgres database using psycopg2 library underneath. See its documentation for more info. - - Args: - query: The query as string, without ending ';' - resource: The path of the resource of type 'postgres' containing the connection info. - The default value is 'g/all/postgres'. It is by convention the default postgres - db of any given workspace. - - Return: - Either None if it is a non returning statement or a list of tuple for statement with return values. - """ - if isinstance(connection, str): - pg_con = wmill.get_resource(connection) - if pg_con is None: - raise Exception(f"Resource {connection} not found") - else: - pg_con = connection - - - conn = psycopg2.connect(**pg_con) - cur = conn.cursor() - cur.execute(f"{query};") - if cur.description: - res = cur.fetchall() - else: - res = None - conn.commit() - cur.close() - conn.close() - return res - diff --git a/python-client/wmill_pg/wmill_pg/py.typed b/python-client/wmill_pg/wmill_pg/py.typed deleted file mode 100644 index 7632ecf775..0000000000 --- a/python-client/wmill_pg/wmill_pg/py.typed +++ /dev/null @@ -1 +0,0 @@ -# Marker file for PEP 561 From 89f835727b982846a8ae47b81d705ac68c3c85ff Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Sun, 1 Mar 2026 18:42:33 +0100 Subject: [PATCH 31/35] chore: use Nix profiles in sandbox Docker image (#8140) * feat: use Nix profiles in sandbox Docker image Replace manual tool installs (rustup, nodesource, curl installers) in sandbox-image/Dockerfile.sandbox with a single `nix profile install .#sandbox`. All tools (Rust, Node, Bun, Deno, Go, gh, sqlx-cli, cargo-watch, Chromium, Playwright, etc.) are now managed declaratively via flake.nix. - Add `packages.sandbox` and `packages.sandbox-full` buildEnv outputs to flake.nix - Add `sandbox-env` helper script for browser tooling env vars - Update playwrightWrapper to export PLAYWRIGHT_BROWSERS_PATH - Rewrite Dockerfile.sandbox: Nix replaces ~50 lines of manual installs - Update entrypoint.sh to source Nix profile PATH - Delete deprecated root Dockerfile.sandbox Co-Authored-By: Claude Opus 4.6 * fix: sandbox image runs as non-root user with wmdev - Rewrite entrypoint.sh to start PostgreSQL as current user (no chown/su needed), fixing "Operation not permitted" when wmdev runs containers with --user - Add chmod -R 777 /root and passwd entry for UID 1000 so non-root containers can access bashrc, nix-profile, and tool configs - Remove apt postgresql server (Nix profile provides it) - Fix bash history expansion errors from literal `!` in system prompt - Fix asciinema path reference (available on PATH, not hardcoded) Co-Authored-By: Claude Opus 4.6 * fix: wrap pkg-config in sandbox profiles to bake in Nix search path Co-Authored-By: Claude Opus 4.6 * fix: add openssh-client and sudo to sandbox image for full root access Co-Authored-By: Claude Opus 4.5 * fix: use useradd instead of manual passwd entry for sandbox agent user Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .wmdev.yaml | 8 +- Dockerfile.sandbox | 231 ------------------------------- flake.nix | 49 +++++++ sandbox-image/Dockerfile.sandbox | 77 +++++------ sandbox-image/entrypoint.sh | 24 +++- 5 files changed, 100 insertions(+), 289 deletions(-) delete mode 100644 Dockerfile.sandbox diff --git a/.wmdev.yaml b/.wmdev.yaml index 5288efb82a..2b556fc0cc 100644 --- a/.wmdev.yaml +++ b/.wmdev.yaml @@ -53,12 +53,11 @@ profiles: --endpoint-url "$(printenv R2_ENDPOINT)" 3) The public URL will be: $(printenv R2_PUBLIC_URL)//screenshot.png - 4) Include screenshots in PR descriptions as markdown images: - ![description]($(printenv R2_PUBLIC_URL)//screenshot.png) + 4) Include in PR descriptions using markdown image syntax. --- Terminal Recordings (asciinema) --- You can record terminal sessions and upload them for sharing. - asciinema is pre-installed at /usr/local/bin/asciinema. + asciinema is available on PATH. 1) Write a shell script with the commands to demo. Add sleep delays for readable pacing: @@ -98,8 +97,7 @@ profiles: 4) The public URL will be: $(printenv R2_PUBLIC_URL)//diagram.svg - 5) Include in PR descriptions as markdown images: - ![description]($(printenv R2_PUBLIC_URL)//diagram.svg) + 5) Include in PR descriptions using markdown image syntax. linkedRepos: - repo: windmill-labs/windmill-ee-private diff --git a/Dockerfile.sandbox b/Dockerfile.sandbox deleted file mode 100644 index 64aa39855e..0000000000 --- a/Dockerfile.sandbox +++ /dev/null @@ -1,231 +0,0 @@ -FROM debian:bookworm-slim - -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - ca-certificates \ - git \ - iptables \ - gosu \ - sudo \ - unzip \ - # Rust native build deps (for cargo check) - pkg-config \ - cmake \ - clang \ - mold \ - libtool \ - libssl-dev \ - libxml2-dev \ - libxmlsec1-dev \ - libxslt1-dev \ - libffi-dev \ - zlib1g-dev \ - libcurl4-openssl-dev \ - libclang-dev \ - libkrb5-dev \ - libsasl2-dev \ - # PostgreSQL (for local DB during development) - postgresql \ - postgresql-client \ - # Node.js 22 (for npm run check / frontend dev) - && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* \ - # Container runs as arbitrary UIDs (--user uid:gid). These three lines make - # sudo work for any UID: - # 1) NOPASSWD rule so sudo never prompts for a password - # 2) Writable passwd/group so the entrypoint can register the dynamic UID - # 3) Writable shadow so unix_chkpwd can validate the account (without this, - # sudo fails with "account validation failure, is your account locked?") - && echo "ALL ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/sandbox \ - && chmod 0440 /etc/sudoers.d/sandbox \ - && chmod 666 /etc/passwd /etc/group /etc/shadow - -# ── GitHub CLI (for PR creation) ────────────────────────────────────────────── -RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ - -o /usr/share/keyrings/githubcli-archive-keyring.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ - > /etc/apt/sources.list.d/github-cli.list \ - && apt-get update && apt-get install -y --no-install-recommends gh \ - && rm -rf /var/lib/apt/lists/* - -# ── Rust toolchain ──────────────────────────────────────────────────────────── -# Install under /usr/local/lib/ so bins are world-readable with default umask. -# CARGO_HOME is overridden to /tmp/.cargo at the end for mutable runtime state. -ENV RUSTUP_HOME=/usr/local/lib/rustup CARGO_HOME=/usr/local/lib/cargo -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --default-toolchain stable --profile minimal && \ - ln -s /usr/local/lib/cargo/bin/* /usr/local/bin/ -RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres && \ - cargo install cargo-watch && \ - cargo install --locked --git https://github.com/asciinema/asciinema && \ - ln -sf /usr/local/lib/cargo/bin/sqlx /usr/local/bin/sqlx && \ - ln -sf /usr/local/lib/cargo/bin/cargo-watch /usr/local/bin/cargo-watch && \ - ln -sf /usr/local/lib/cargo/bin/asciinema /usr/local/bin/asciinema - -# ── Register dynamic runtime users ─────────────────────────────────────────── -RUN cat <<'SCRIPT' > /usr/local/bin/register-dynamic-user.sh -#!/bin/sh -set -eu - -uid="${1:-}" -gid="${2:-}" - -if [ -z "$uid" ] || [ -z "$gid" ]; then - echo "register-dynamic-user: usage: register-dynamic-user " >&2 - exit 1 -fi - -if ! getent group "$gid" >/dev/null 2>&1; then - echo "sandbox:x:${gid}:" >> /etc/group -fi - -if ! getent passwd "$uid" >/dev/null 2>&1; then - echo "sandbox:x:${uid}:${gid}:sandbox:/tmp:/bin/sh" >> /etc/passwd -fi - -# Add a shadow entry ("*" = no password) so unix_chkpwd doesn't reject sudo. -if ! grep -q "^sandbox:" /etc/shadow 2>/dev/null; then - echo "sandbox:*:19000:0:99999:7:::" >> /etc/shadow -fi -SCRIPT -RUN chmod +x /usr/local/bin/register-dynamic-user.sh - -# ── Network init script (iptables firewall + privilege drop) ────────────────── -RUN cat <<'SCRIPT' > /usr/local/bin/network-init.sh -#!/bin/bash -set -euo pipefail - -if [ -n "${WM_PROXY_HOST:-}" ] && [ -n "${WM_PROXY_PORT:-}" ]; then - # Resolve hostnames to ALL IPs (multi-A records, round-robin DNS) - PROXY_IPS=$(getent ahostsv4 "$WM_PROXY_HOST" | awk '{print $1}' | sort -u) - RPC_HOST="${WM_RPC_HOST:-$WM_PROXY_HOST}" - RPC_IPS=$(getent ahostsv4 "$RPC_HOST" | awk '{print $1}' | sort -u) - - if [ -z "$PROXY_IPS" ] || [ -z "$RPC_IPS" ]; then - echo "network-init: failed to resolve proxy/RPC host" >&2 - exit 1 - fi - - # IPv4: default deny outbound - iptables -P OUTPUT DROP - iptables -A OUTPUT -o lo -j ACCEPT - iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT - - # Allow DNS (UDP/TCP 53) to configured nameservers. - if [ -f /etc/resolv.conf ]; then - grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | while read -r ns; do - iptables -A OUTPUT -d "$ns" -p udp --dport 53 -j ACCEPT - iptables -A OUTPUT -d "$ns" -p tcp --dport 53 -j ACCEPT - done - fi - - # Allow ALL resolved proxy IPs (handles multi-A DNS) - for ip in $PROXY_IPS; do - iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_PROXY_PORT" -j ACCEPT - done - - # Allow ALL resolved RPC IPs - if [ -n "${WM_RPC_PORT:-}" ]; then - for ip in $RPC_IPS; do - iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_RPC_PORT" -j ACCEPT - done - fi - - # Reject (not drop) everything else to fail fast instead of hanging - iptables -A OUTPUT -j REJECT - - # IPv6: block entirely to prevent leaks (fail closed) - if ip6tables -L -n >/dev/null 2>&1; then - ip6tables -P OUTPUT DROP - ip6tables -A OUTPUT -o lo -j ACCEPT - ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT - ip6tables -A OUTPUT -j REJECT - else - if ! sysctl -w net.ipv6.conf.all.disable_ipv6=1 2>/dev/null; then - echo "network-init: failed to block IPv6 (neither ip6tables nor sysctl available)" >&2 - exit 1 - fi - fi -fi - -# Add sandbox user/group so sudo works after dropping privileges. -if [ -z "${WM_TARGET_UID:-}" ] || [ -z "${WM_TARGET_GID:-}" ]; then - echo "network-init: WM_TARGET_UID and WM_TARGET_GID are required" >&2 - exit 1 -fi -/usr/local/bin/register-dynamic-user.sh "${WM_TARGET_UID}" "${WM_TARGET_GID}" - -# Fix PTY ownership so the unprivileged user can read/write the terminal. -if [ -t 0 ]; then - chown "${WM_TARGET_UID}:${WM_TARGET_GID}" "$(tty)" -fi - -# Drop privileges and exec the user command. -exec gosu "${WM_TARGET_UID}:${WM_TARGET_GID}" env HOME=/tmp "$@" -SCRIPT -RUN chmod +x /usr/local/bin/network-init.sh - -# ── workmux (sandbox RPC) ──────────────────────────────────────────────────── -RUN curl -fsSL https://raw.githubusercontent.com/raine/workmux/main/scripts/install.sh | bash - -# ── Claude Code ─────────────────────────────────────────────────────────────── -RUN curl -fsSL https://claude.ai/install.sh | bash && \ - target="$(readlink -f /root/.local/bin/claude)" && \ - mv /root/.local/share/claude /usr/local/lib/claude && \ - ln -s "/usr/local/lib/claude/versions/$(basename "$target")" /usr/local/bin/claude && \ - mkdir -p /tmp/.local/bin && \ - ln -s /usr/local/bin/claude /tmp/.local/bin/claude && \ - chmod -R a+rwX /tmp/.local - -# ── Codex ───────────────────────────────────────────────────────────────────── -RUN npm i -g @openai/codex - -# ── Bun ─────────────────────────────────────────────────────────────────────── -ENV BUN_INSTALL=/usr/local/lib/bun -RUN curl -fsSL https://bun.sh/install | bash && \ - ln -s /usr/local/lib/bun/bin/bun /usr/local/bin/bun && \ - ln -s /usr/local/lib/bun/bin/bunx /usr/local/bin/bunx - -# ── Playwright + Chromium (for screenshots) ────────────────────────────────── -ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/lib/playwright-browsers -RUN bun add -g @playwright/test \ - && bunx playwright install chromium --with-deps \ - && chmod -R a+rwX /usr/local/lib/playwright-browsers \ - && chmod -R a+rwX /usr/local/lib/bun/install \ - && rm -rf /var/lib/apt/lists/* /tmp/bunx-* - - -# ── Runtime env for arbitrary UID ───────────────────────────────────────────── -# Mutable state goes to /tmp (writable by any UID). Toolchains stay read-only. -ENV CARGO_HOME=/tmp/.cargo BUN_TMPDIR=/tmp - -# ── Entrypoint ──────────────────────────────────────────────────────────────── -RUN cat <<'ENTRY' > /usr/local/bin/entrypoint.sh -#!/bin/sh -/usr/local/bin/register-dynamic-user.sh "$(id -u)" "$(id -g)" - -# Start PostgreSQL (unix socket in /tmp, owned by postgres user) -mkdir -p /tmp/pgdata && sudo chown postgres:postgres /tmp/pgdata -if [ ! -f /tmp/pgdata/PG_VERSION ]; then - sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /tmp/pgdata --auth=trust -fi -sudo -u postgres /usr/lib/postgresql/15/bin/pg_ctl -D /tmp/pgdata -l /tmp/pg.log start -o "-k /tmp" -sudo -u postgres psql -h /tmp -c "CREATE ROLE sandbox SUPERUSER LOGIN" 2>/dev/null || true -sudo -u postgres createdb -h /tmp windmill 2>/dev/null || true - -# Run database migrations so sqlx compile-time checks work -if [ -d "$PWD/backend/migrations" ]; then - DATABASE_URL="postgres://sandbox@localhost/windmill?host=/tmp" \ - sqlx migrate run --source "$PWD/backend/migrations" 2>/dev/null || true -fi - -# Install frontend dependencies and generate backend client -if [ -d "$PWD/frontend" ]; then - (cd "$PWD/frontend" && npm install && npm run generate-backend-client) 2>/dev/null || true -fi - -exec "$@" -ENTRY -RUN chmod +x /usr/local/bin/entrypoint.sh -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] \ No newline at end of file diff --git a/flake.nix b/flake.nix index 34ecb689a1..135005b83d 100644 --- a/flake.nix +++ b/flake.nix @@ -342,11 +342,60 @@ # Wrapper for the Nix-provided playwright CLI (version-matched to its browsers) playwrightWrapper = pkgs.writeShellScriptBin "playwright" '' + export PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}" exec ${pkgs.nodejs}/bin/node ${pkgs.playwright-driver}/cli.js "$@" ''; + # --------------------------------------------------------------- + # sandbox-env script — outputs env vars for browser tooling + # Usage: eval "$(sandbox-env)" + # --------------------------------------------------------------- + + sandboxEnvScript = pkgs.writeShellScriptBin "sandbox-env" '' + echo "export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}" + echo "export PUPPETEER_EXECUTABLE_PATH=${pkgs.chromium}/bin/chromium" + echo "export PUPPETEER_SKIP_DOWNLOAD=true" + ''; + + # --------------------------------------------------------------- + # pkg-config wrapper — bakes in the Nix pkg-config search path + # so sandbox profiles (buildEnv) work without setting env vars. + # --------------------------------------------------------------- + + pkgConfigWrapper = pkgs.writeShellScriptBin "pkg-config" '' + export PKG_CONFIG_PATH="${pkgConfigPath}:$PKG_CONFIG_PATH" + exec ${pkgs.pkg-config}/bin/pkg-config "$@" + ''; + + # --------------------------------------------------------------- + # Installable sandbox profiles (nix profile install .#sandbox) + # --------------------------------------------------------------- + + sandboxEnv = pkgs.buildEnv { + name = "windmill-sandbox"; + paths = coreBuildInputs ++ helperScriptsBase + ++ [ playwrightWrapper sandboxEnvScript pkgConfigWrapper pkgs.chromium ]; + }; + + sandboxFullEnv = pkgs.buildEnv { + name = "windmill-sandbox-full"; + paths = coreBuildInputs ++ extraRuntimes + ++ helperScriptsBase ++ helperScriptsFull + ++ [ playwrightWrapper sandboxEnvScript pkgConfigWrapper pkgs.chromium + pkgs.cargo-sweep pkgs.xcaddy pkgs.nsjail ]; + }; + in { + # ============================================================= + # Installable profiles — for Docker / nix profile install + # Usage: nix profile install .#sandbox + # ============================================================= + + packages.sandbox = sandboxEnv; + packages.sandbox-full = sandboxFullEnv; + packages.default = sandboxEnv; + # ============================================================= # default — daily driver for backend + frontend development # Usage: nix develop diff --git a/sandbox-image/Dockerfile.sandbox b/sandbox-image/Dockerfile.sandbox index aaf71620d0..7c9ca181a4 100644 --- a/sandbox-image/Dockerfile.sandbox +++ b/sandbox-image/Dockerfile.sandbox @@ -1,61 +1,46 @@ FROM debian:bookworm-slim -# ── System packages ─────────────────────────────────────────────────────────── +# ── Minimal system deps ────────────────────────────────────────────────────── RUN apt-get update && apt-get install -y --no-install-recommends \ - curl ca-certificates git unzip openssh-client \ - # Rust native build deps - pkg-config cmake clang mold libtool \ - libssl-dev libxml2-dev libxmlsec1-dev libxslt1-dev \ - libffi-dev zlib1g-dev libcurl4-openssl-dev libclang-dev \ - libkrb5-dev libsasl2-dev \ - # PostgreSQL - postgresql postgresql-client \ + ca-certificates curl git openssh-client sudo xz-utils \ + # PostgreSQL client (server provided by Nix profile) + postgresql-client \ && rm -rf /var/lib/apt/lists/* -# ── Node.js 22 ──────────────────────────────────────────────────────────────── -RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* +# ── Nix (single-user, Determinate installer) ──────────────────────────────── +RUN curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install linux --no-confirm --init none +ENV PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH" -# ── GitHub CLI ──────────────────────────────────────────────────────────────── -RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ - -o /usr/share/keyrings/githubcli-archive-keyring.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ - > /etc/apt/sources.list.d/github-cli.list \ - && apt-get update && apt-get install -y --no-install-recommends gh \ - && rm -rf /var/lib/apt/lists/* +# ── Install default sandbox profile ───────────────────────────────────────── +COPY flake.nix flake.lock /tmp/flake/ +RUN cd /tmp/flake && nix profile install .#sandbox \ + && rm -rf /tmp/flake -# ── Rust toolchain ──────────────────────────────────────────────────────────── -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --default-toolchain stable --profile minimal -ENV PATH="/root/.cargo/bin:$PATH" +# ── Browser env (Puppeteer / Mermaid point to Nix Chromium) ───────────────── +ENV PUPPETEER_SKIP_DOWNLOAD="true" +RUN printf '{"args":["--no-sandbox","--disable-setuid-sandbox"],"executablePath":"%s"}\n' \ + "$(readlink -f "$(which chromium)")" > /root/.puppeteerrc.json -RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres \ - && cargo install cargo-watch - -# ── Bun ─────────────────────────────────────────────────────────────────────── -RUN curl -fsSL https://bun.sh/install | bash -ENV PATH="/root/.bun/bin:$PATH" - -# ── Playwright + Chromium ───────────────────────────────────────────────────── -RUN bun add -g @playwright/test \ - && bunx playwright install chromium --with-deps \ - && rm -rf /var/lib/apt/lists/* /tmp/bunx-* - -# ── Claude Code ─────────────────────────────────────────────────────────────── +# ── Claude Code ────────────────────────────────────────────────────────────── RUN curl -fsSL https://claude.ai/install.sh | bash ENV PATH="/root/.local/bin:$PATH" -# ── Codex ───────────────────────────────────────────────────────────────────── +# ── npm globals (install to /usr/local so bins land on PATH) ───────────────── +ENV NPM_CONFIG_PREFIX=/usr/local RUN npm i -g @openai/codex +RUN PUPPETEER_SKIP_DOWNLOAD=true npm i -g @mermaid-js/mermaid-cli -# ── Mermaid CLI ─────────────────────────────────────────────────────────────── -# Skip puppeteer's own Chromium download; reuse the one Playwright already installed above -RUN PUPPETEER_SKIP_DOWNLOAD=true npm i -g @mermaid-js/mermaid-cli \ - && CHROMIUM=$(find /root/.cache/ms-playwright -name 'chrome' -executable -type f | head -1) \ - && printf '{"args":["--no-sandbox","--disable-setuid-sandbox"],"executablePath":"%s"}\n' "$CHROMIUM" \ - > /root/.puppeteerrc.json +# ── Runtime env ────────────────────────────────────────────────────────────── +ENV CARGO_HOME=/tmp/.cargo -# ── Entrypoint (run explicitly via docker exec, not on container start) ────── -COPY entrypoint.sh /usr/local/bin/entrypoint.sh +# ── Allow non-root UID (--user) to access tools installed in /root ─────────── +# Give UID 1000 a proper passwd entry, passwordless sudo, and full access to /root +RUN chmod -R 777 /root \ + && useradd -u 1000 -g 100 -d /root -s /bin/bash -M agent \ + && echo "agent ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/agent \ + && chmod 0440 /etc/sudoers.d/agent + +# ── Entrypoint ─────────────────────────────────────────────────────────────── +COPY sandbox-image/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh diff --git a/sandbox-image/entrypoint.sh b/sandbox-image/entrypoint.sh index 24f384bcf5..b5a0e4b14e 100644 --- a/sandbox-image/entrypoint.sh +++ b/sandbox-image/entrypoint.sh @@ -1,21 +1,31 @@ #!/bin/bash set -euo pipefail -# ── Start PostgreSQL ────────────────────────────────────────────────────────── +# ── Nix profile ────────────────────────────────────────────────────────────── +export PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH" +export CARGO_HOME=/tmp/.cargo + +# ── Browser env (from Nix sandbox profile) ─────────────────────────────────── +if command -v sandbox-env >/dev/null 2>&1; then + eval "$(sandbox-env)" +fi + +# ── Start PostgreSQL as current user (no root/su needed) ───────────────────── PGDATA=/tmp/pgdata mkdir -p "$PGDATA" -chown postgres:postgres "$PGDATA" if [ ! -f "$PGDATA/PG_VERSION" ]; then - su - postgres -c "/usr/lib/postgresql/15/bin/initdb -D $PGDATA --auth=trust" + initdb -D "$PGDATA" --auth=trust fi -su - postgres -c "/usr/lib/postgresql/15/bin/pg_ctl -D $PGDATA -l /tmp/pg.log start -o '-k /tmp'" -su - postgres -c "psql -h /tmp -c 'CREATE ROLE root SUPERUSER LOGIN'" 2>/dev/null || true -su - postgres -c "createdb -h /tmp windmill" 2>/dev/null || true +pg_ctl -D "$PGDATA" -l /tmp/pg.log start -o "-k /tmp" + +# Create postgres role and windmill database (idempotent) +psql -h /tmp -d postgres -c "CREATE ROLE postgres SUPERUSER LOGIN" 2>/dev/null || true +createdb -h /tmp windmill 2>/dev/null || true # ── Run migrations if present ───────────────────────────────────────────────── if [ -d "$PWD/backend/migrations" ]; then - DATABASE_URL="postgres://root@localhost/windmill?host=/tmp" \ + DATABASE_URL="postgres://postgres@localhost/windmill?host=/tmp" \ sqlx migrate run --source "$PWD/backend/migrations" 2>/dev/null || true fi From c70307d3f2dfe61a0250dd12234470a25baf2d1b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 1 Mar 2026 23:21:06 +0000 Subject: [PATCH 32/35] fix: show sync endpoint timeout setting on all instances (#8170) Co-authored-by: Claude Opus 4.5 --- frontend/src/lib/components/instanceSettings.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index 2b59f64f75..de4cd1528b 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -231,7 +231,6 @@ export const settings: Record = { description: 'Maximum amount of time (measured in seconds) that a sync endpoint is allowed to run before it is forcibly stopped or timed out.', key: 'timeout_wait_result', - cloudonly: true, fieldType: 'seconds', placeholder: '60', storage: 'setting' From fca94f88dd796db66e0c5bd0225e23b92efce4a7 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 2 Mar 2026 07:30:36 +0000 Subject: [PATCH 33/35] fix: add missing grant_types field to instance config OAuth structs (#8175) Co-authored-by: Claude Opus 4.6 --- backend/windmill-common/src/instance_config.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index 89df71824f..8bb298ba0c 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -420,6 +420,8 @@ pub struct OAuthClient { pub login_config: Option, #[serde(skip_serializing_if = "Option::is_none")] pub share_with_workspaces: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub grant_types: Vec, } /// OAuth provider endpoint configuration. @@ -438,6 +440,8 @@ pub struct OAuthConfig { pub extra_params_callback: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub req_body_auth: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub grant_types: Vec, } // --------------------------------------------------------------------------- @@ -2235,6 +2239,7 @@ mod tests { connect_config: None, login_config: None, share_with_workspaces: None, + grant_types: vec![], }, ); m From db44b8be74e1709dbf759dd391bdb3861b3c711b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 2 Mar 2026 07:35:40 +0000 Subject: [PATCH 34/35] fix: add missing display_name and tenant fields to instance config OAuthClient (#8176) * fix: add missing grant_types field to instance config OAuth structs Co-Authored-By: Claude Opus 4.6 * fix: add missing display_name and tenant fields to instance config OAuthClient Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- backend/windmill-common/src/instance_config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index 8bb298ba0c..c31fd01897 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -413,12 +413,16 @@ pub struct OAuthClient { pub id: String, pub secret: StringOrSecretRef, #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub allowed_domains: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub connect_config: Option, #[serde(skip_serializing_if = "Option::is_none")] pub login_config: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub tenant: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub share_with_workspaces: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub grant_types: Vec, @@ -2235,9 +2239,11 @@ mod tests { secret: StringOrSecretRef::EnvRef(EnvRefWrapper { env_ref: "__WM_TEST_OAUTH_SECRET".to_string(), }), + display_name: None, allowed_domains: None, connect_config: None, login_config: None, + tenant: None, share_with_workspaces: None, grant_types: vec![], }, From 889c98b38bfe6f78b4ca993db32205dd297739da Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 2 Mar 2026 07:40:38 +0000 Subject: [PATCH 35/35] chore(main): release 1.647.1 (#8171) * chore(main): release 1.647.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 9 ++ backend/Cargo.lock | 148 +++++++++--------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 2 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 15 files changed, 98 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8276e8d8d1..f1d6d7d4a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.647.1](https://github.com/windmill-labs/windmill/compare/v1.647.0...v1.647.1) (2026-03-02) + + +### Bug Fixes + +* add missing display_name and tenant fields to instance config OAuthClient ([#8176](https://github.com/windmill-labs/windmill/issues/8176)) ([db44b8b](https://github.com/windmill-labs/windmill/commit/db44b8be74e1709dbf759dd391bdb3861b3c711b)) +* add missing grant_types field to instance config OAuth structs ([#8175](https://github.com/windmill-labs/windmill/issues/8175)) ([fca94f8](https://github.com/windmill-labs/windmill/commit/fca94f88dd796db66e0c5bd0225e23b92efce4a7)) +* show sync endpoint timeout setting on all instances ([#8170](https://github.com/windmill-labs/windmill/issues/8170)) ([c70307d](https://github.com/windmill-labs/windmill/commit/c70307d3f2dfe61a0250dd12234470a25baf2d1b)) + ## [1.647.0](https://github.com/windmill-labs/windmill/compare/v1.646.0...v1.647.0) (2026-03-01) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 8d4a5aa431..e6558f5870 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -8600,9 +8600,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.13" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" dependencies = [ "async-lock", "crossbeam-channel", @@ -12593,9 +12593,9 @@ checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" [[package]] name = "sketches-ddsketch" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" dependencies = [ "serde", ] @@ -15732,7 +15732,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-nats", @@ -15796,7 +15796,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15809,7 +15809,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "argon2", @@ -15947,7 +15947,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15970,7 +15970,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -15983,7 +15983,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16009,7 +16009,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.647.0" +version = "1.647.1" dependencies = [ "reqwest 0.12.28", "serde", @@ -16019,7 +16019,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16036,7 +16036,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16059,7 +16059,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16082,7 +16082,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16098,7 +16098,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16118,7 +16118,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16138,7 +16138,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16152,7 +16152,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-nats", @@ -16179,7 +16179,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16204,7 +16204,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "flate2", @@ -16222,7 +16222,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16243,7 +16243,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16263,7 +16263,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16293,7 +16293,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16320,7 +16320,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.647.0" +version = "1.647.1" dependencies = [ "lazy_static", "serde", @@ -16332,7 +16332,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.647.0" +version = "1.647.1" dependencies = [ "argon2", "axum 0.7.9", @@ -16355,7 +16355,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16369,7 +16369,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.647.0" +version = "1.647.1" dependencies = [ "axum 0.7.9", "chrono", @@ -16399,7 +16399,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.647.0" +version = "1.647.1" dependencies = [ "chrono", "lazy_static", @@ -16413,7 +16413,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -16432,7 +16432,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.647.0" +version = "1.647.1" dependencies = [ "aes-gcm", "anyhow", @@ -16531,7 +16531,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.647.0" +version = "1.647.1" dependencies = [ "chrono", "itertools 0.14.0", @@ -16550,7 +16550,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.647.0" +version = "1.647.1" dependencies = [ "regex", "serde", @@ -16565,7 +16565,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16589,7 +16589,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "futures", @@ -16606,7 +16606,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.647.0" +version = "1.647.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16622,7 +16622,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -16643,7 +16643,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -16674,7 +16674,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-oauth2", @@ -16698,7 +16698,7 @@ dependencies = [ [[package]] name = "windmill-object-store" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-stream", @@ -16732,7 +16732,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "futures", @@ -16750,7 +16750,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.647.0" +version = "1.647.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -16759,7 +16759,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "lazy_static", @@ -16771,7 +16771,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "serde_json", @@ -16783,7 +16783,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "gosyn", @@ -16795,7 +16795,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "lazy_static", @@ -16807,7 +16807,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "serde_json", @@ -16819,7 +16819,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "nu-parser", @@ -16830,7 +16830,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16841,7 +16841,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16854,7 +16854,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-recursion", @@ -16878,7 +16878,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "lazy_static", @@ -16892,7 +16892,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16909,7 +16909,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "lazy_static", @@ -16924,7 +16924,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "lazy_static", @@ -16943,7 +16943,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "serde", @@ -16954,7 +16954,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-recursion", @@ -16991,7 +16991,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "const_format", @@ -17029,7 +17029,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.647.0" +version = "1.647.1" dependencies = [ "getrandom 0.3.4", "wasm-bindgen", @@ -17040,7 +17040,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-recursion", @@ -17069,7 +17069,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "axum 0.7.9", @@ -17092,7 +17092,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17125,7 +17125,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17145,7 +17145,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17179,7 +17179,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17214,7 +17214,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17237,7 +17237,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17261,7 +17261,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-nats", @@ -17285,7 +17285,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17320,7 +17320,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17348,7 +17348,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-trait", @@ -17371,7 +17371,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "bitflags 2.9.4", @@ -17389,7 +17389,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.647.0" +version = "1.647.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f67a635efc..103d97db1c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.647.0" +version = "1.647.1" authors.workspace = true edition.workspace = true @@ -76,7 +76,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.647.0" +version = "1.647.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index df7a38b7d0..e536aecb0c 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.647.0 + version: 1.647.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index f19c2c98e6..b525ffd7ab 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.647.0"; +export const VERSION = "v1.647.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 489666f1e6..57a9ede3c9 100755 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -67,7 +67,7 @@ export { workspaceAdd, }; -export const VERSION = "1.647.0"; +export const VERSION = "1.647.1"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e49c34127e..4e85ef58e5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.647.0", + "version": "1.647.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.647.0", + "version": "1.647.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 580b6867d5..cf92899030 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.647.0", + "version": "1.647.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 6ece442b78..876db2f672 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.647.0" +wmill = ">=1.647.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 3286540995..725bc4a658 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.647.0 + version: 1.647.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 06af5f0da6..b8e24f942e 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.647.0' + ModuleVersion = '1.647.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index bcbfde941d..0a44ff7453 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.647.0" +version = "1.647.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 275e75a398..e3289413bd 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.647.0", + "version": "1.647.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 2cc6be7679..68658964d2 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.647.0", + "version": "1.647.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index 32f772cdb8..dc81c1ee46 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.647.0 +1.647.1