diff --git a/CHANGELOG.md b/CHANGELOG.md index 15fca3e649..ae23b08efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [1.649.0](https://github.com/windmill-labs/windmill/compare/v1.648.0...v1.649.0) (2026-03-03) + + +### Features + +* **frontend:** add script recorder for offline replay ([#8200](https://github.com/windmill-labs/windmill/issues/8200)) ([c97d8b4](https://github.com/windmill-labs/windmill/commit/c97d8b4715f86ea83ab2c0223ba859ced690829a)) +* move index management out of /srch/, add storage size reporting ([#8169](https://github.com/windmill-labs/windmill/issues/8169)) ([ee01acd](https://github.com/windmill-labs/windmill/commit/ee01acd9a6a2cd68a3f226988bfb46f6a6e64c08)) + + +### Bug Fixes + +* clean up slow-load toast interval on component destroy ([#8207](https://github.com/windmill-labs/windmill/issues/8207)) ([26f4f2b](https://github.com/windmill-labs/windmill/commit/26f4f2b399b828185b553289d6560e12261030a3)) +* **frontend:** prevent subflow expansion from hiding all insertion points ([#8203](https://github.com/windmill-labs/windmill/issues/8203)) ([e97da86](https://github.com/windmill-labs/windmill/commit/e97da860672171e33054a77d71f4824bb09e540d)) +* gracefully handle malformed OAuth entries in instance config ([#8205](https://github.com/windmill-labs/windmill/issues/8205)) ([cac4bdd](https://github.com/windmill-labs/windmill/commit/cac4bdd54f0c3ea80844ac31f7597f418ff7d8ae)) +* skip stop_after_if evaluation for skipped (identity) flow steps ([#8201](https://github.com/windmill-labs/windmill/issues/8201)) ([e6f7775](https://github.com/windmill-labs/windmill/commit/e6f7775d4d9a052aefc37260c6ed161146841cd7)) +* use exact matching for python requirements directive parsing ([#8199](https://github.com/windmill-labs/windmill/issues/8199)) ([2b2be38](https://github.com/windmill-labs/windmill/commit/2b2be38f129bbe58b6bb3815c4bd94aa03a3da90)) + + +### Performance Improvements + +* use two-step query in input history to leverage v2_job index ([#8197](https://github.com/windmill-labs/windmill/issues/8197)) ([50defdd](https://github.com/windmill-labs/windmill/commit/50defdded113b4d2cf0991b3fb642d1cd9a462b7)) + ## [1.648.0](https://github.com/windmill-labs/windmill/compare/v1.647.2...v1.648.0) (2026-03-02) diff --git a/backend/.sqlx/query-19a7ebb2e7e8e57b6e7c974da8eb7c6841a5c4ff12ba7c12c73d691c49dd99ed.json b/backend/.sqlx/query-19a7ebb2e7e8e57b6e7c974da8eb7c6841a5c4ff12ba7c12c73d691c49dd99ed.json new file mode 100644 index 0000000000..18ad13d90f --- /dev/null +++ b/backend/.sqlx/query-19a7ebb2e7e8e57b6e7c974da8eb7c6841a5c4ff12ba7c12c73d691c49dd99ed.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT email FROM token WHERE token = $1 AND (expiration > NOW() OR expiration IS NULL)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "19a7ebb2e7e8e57b6e7c974da8eb7c6841a5c4ff12ba7c12c73d691c49dd99ed" +} diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 713ccb9dd3..36ddb8ab9f 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - null + true ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/Cargo.lock b/backend/Cargo.lock index a2a27a9413..d26d5b76d7 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -860,9 +860,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", "zeroize", @@ -870,9 +870,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" dependencies = [ "cc", "cmake", @@ -1334,9 +1334,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.14" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53543b4b86ed43f051644f704a98c7291b3618b67adf057ee77a366fa52fcaa" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" dependencies = [ "xmlparser", ] @@ -6173,20 +6173,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -7421,9 +7421,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "ipnetwork" @@ -10723,6 +10723,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -13850,7 +13856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -15732,7 +15738,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-nats", @@ -15796,7 +15802,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15809,7 +15815,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "argon2", @@ -15947,7 +15953,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15970,7 +15976,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -15983,7 +15989,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16009,7 +16015,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.648.0" +version = "1.649.0" dependencies = [ "reqwest 0.12.28", "serde", @@ -16019,7 +16025,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16036,7 +16042,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "base64 0.22.1", @@ -16059,7 +16065,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16082,7 +16088,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16098,7 +16104,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16118,7 +16124,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16138,7 +16144,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16152,7 +16158,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-nats", @@ -16179,7 +16185,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16204,7 +16210,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "flate2", @@ -16222,7 +16228,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16243,7 +16249,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16263,7 +16269,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16293,7 +16299,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16320,7 +16326,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.648.0" +version = "1.649.0" dependencies = [ "lazy_static", "serde", @@ -16332,7 +16338,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.648.0" +version = "1.649.0" dependencies = [ "argon2", "axum 0.7.9", @@ -16355,7 +16361,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16369,7 +16375,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.648.0" +version = "1.649.0" dependencies = [ "axum 0.7.9", "chrono", @@ -16399,7 +16405,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.648.0" +version = "1.649.0" dependencies = [ "chrono", "lazy_static", @@ -16413,7 +16419,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -16432,7 +16438,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.648.0" +version = "1.649.0" dependencies = [ "aes-gcm", "anyhow", @@ -16531,7 +16537,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.648.0" +version = "1.649.0" dependencies = [ "chrono", "itertools 0.14.0", @@ -16550,7 +16556,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.648.0" +version = "1.649.0" dependencies = [ "regex", "serde", @@ -16565,7 +16571,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "astral-tokio-tar", @@ -16589,7 +16595,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "futures", @@ -16606,7 +16612,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.648.0" +version = "1.649.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -16622,7 +16628,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -16643,7 +16649,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -16674,7 +16680,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-oauth2", @@ -16698,7 +16704,7 @@ dependencies = [ [[package]] name = "windmill-object-store" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-stream", @@ -16732,7 +16738,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "futures", @@ -16750,7 +16756,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.648.0" +version = "1.649.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -16759,7 +16765,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "lazy_static", @@ -16771,7 +16777,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "serde_json", @@ -16783,7 +16789,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "gosyn", @@ -16795,7 +16801,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "lazy_static", @@ -16807,7 +16813,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "serde_json", @@ -16819,7 +16825,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "nu-parser", @@ -16830,7 +16836,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16841,7 +16847,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -16854,7 +16860,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-recursion", @@ -16878,7 +16884,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "lazy_static", @@ -16892,7 +16898,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -16909,7 +16915,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "lazy_static", @@ -16924,7 +16930,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "lazy_static", @@ -16943,7 +16949,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "serde", @@ -16954,7 +16960,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-recursion", @@ -16991,7 +16997,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "const_format", @@ -17029,7 +17035,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.648.0" +version = "1.649.0" dependencies = [ "getrandom 0.3.4", "wasm-bindgen", @@ -17040,7 +17046,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-recursion", @@ -17069,7 +17075,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "axum 0.7.9", @@ -17092,7 +17098,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17125,7 +17131,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17145,7 +17151,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17179,7 +17185,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17214,7 +17220,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17237,7 +17243,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17261,7 +17267,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-nats", @@ -17285,7 +17291,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17320,7 +17326,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17348,7 +17354,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-trait", @@ -17371,7 +17377,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "bitflags 2.9.4", @@ -17389,7 +17395,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.648.0" +version = "1.649.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2d964c45b4..195d8bd30d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.648.0" +version = "1.649.0" authors.workspace = true edition.workspace = true @@ -76,7 +76,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.648.0" +version = "1.649.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index e0d990384a..a71cab586d 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -9b3339730eb4bb0b564c7c56ac546f33fb3d8905 \ No newline at end of file +6fd5a2ce908235a17975ad4dbdf0051cd89334f3 diff --git a/backend/src/main.rs b/backend/src/main.rs index c4440b7f34..97980484a5 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -62,7 +62,7 @@ use windmill_common::{ }, worker::{ is_native_mode_from_env, reload_custom_tags_setting, Connection, HUB_CACHE_DIR, - HUB_RT_CACHE_DIR, NATIVE_MODE_RESOLVED, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP, + HUB_RT_CACHE_DIR, NATIVE_MODE_RESOLVED, TMP_LOGS_DIR, WINDMILL_DIR, WORKER_GROUP, }, KillpillSender, DEFAULT_HUB_BASE_URL, METRICS_ENABLED, }; @@ -238,8 +238,8 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> { ) })?; - create_dir_all(HUB_CACHE_DIR)?; - create_dir_all(BUN_BUNDLE_CACHE_DIR)?; + create_dir_all(&*HUB_CACHE_DIR)?; + create_dir_all(&*BUN_BUNDLE_CACHE_DIR)?; for path in paths.values() { tracing::info!("Caching hub script at {path}"); @@ -249,7 +249,7 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> { .as_ref() .is_some_and(|x| x == &ScriptLang::Deno) { - let job_dir = format!("{}/cache_init/{}", TMP_DIR, Uuid::new_v4()); + let job_dir = format!("{}/cache_init/{}", *WINDMILL_DIR, Uuid::new_v4()); create_dir_all(&job_dir)?; let _ = windmill_worker::generate_deno_lock( &Uuid::nil(), @@ -267,7 +267,7 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> { tokio::fs::remove_dir_all(job_dir).await?; } else if res.language.as_ref().is_some_and(|x| x == &ScriptLang::Bun) { let job_id = Uuid::new_v4(); - let job_dir = format!("{}/cache_init/{}", TMP_DIR, job_id); + let job_dir = format!("{}/cache_init/{}", *WINDMILL_DIR, job_id); create_dir_all(&job_dir)?; if let Some(lock) = res.lockfile { let _ = windmill_worker::prepare_job_dir(&lock, &job_dir).await?; @@ -384,9 +384,9 @@ async fn cache_hub_resource_types() -> anyhow::Result<()> { println!("Fetched {} resource types from hub", resource_types.len()); - create_dir_all(HUB_RT_CACHE_DIR)?; + create_dir_all(&*HUB_RT_CACHE_DIR)?; - let cache_path = format!("{}/{}", HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE); + let cache_path = format!("{}/{}", *HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE); let content = serde_json::to_string_pretty(&resource_types) .with_context(|| "Failed to serialize resource types")?; @@ -398,7 +398,7 @@ async fn cache_hub_resource_types() -> anyhow::Result<()> { } pub async fn sync_cached_resource_types(db: &sqlx::Pool) -> anyhow::Result<()> { - let cache_path = format!("{}/{}", HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE); + let cache_path = format!("{}/{}", *HUB_RT_CACHE_DIR, HUB_RT_CACHE_FILE); if tokio::fs::metadata(&cache_path).await.is_err() { tracing::info!( @@ -969,7 +969,7 @@ Windmill Community Edition {GIT_VERSION} DirBuilder::new() .recursive(true) - .create("/tmp/windmill") + .create(&*WINDMILL_DIR) .expect("could not create initial server dir"); #[cfg(feature = "tantivy")] @@ -1794,27 +1794,27 @@ pub async fn run_workers( let mut handles = Vec::with_capacity(num_workers as usize); for x in [ - TMP_LOGS_DIR, - UV_CACHE_DIR, - DENO_CACHE_DIR, - DENO_CACHE_DIR_DEPS, - DENO_CACHE_DIR_NPM, - BUN_CACHE_DIR, - PY310_CACHE_DIR, - PY311_CACHE_DIR, - PY312_CACHE_DIR, - PY313_CACHE_DIR, - BUN_BUNDLE_CACHE_DIR, - GO_CACHE_DIR, - GO_BIN_CACHE_DIR, - RUST_CACHE_DIR, - CSHARP_CACHE_DIR, - NU_CACHE_DIR, - HUB_CACHE_DIR, - POWERSHELL_CACHE_DIR, - JAVA_CACHE_DIR, - RUBY_CACHE_DIR, - TAR_JAVA_CACHE_DIR, // for related places search: ADD_NEW_LANG + &*TMP_LOGS_DIR, + &*UV_CACHE_DIR, + &*DENO_CACHE_DIR, + &*DENO_CACHE_DIR_DEPS, + &*DENO_CACHE_DIR_NPM, + &*BUN_CACHE_DIR, + &*PY310_CACHE_DIR, + &*PY311_CACHE_DIR, + &*PY312_CACHE_DIR, + &*PY313_CACHE_DIR, + &*BUN_BUNDLE_CACHE_DIR, + &*GO_CACHE_DIR, + &*GO_BIN_CACHE_DIR, + &*RUST_CACHE_DIR, + &*CSHARP_CACHE_DIR, + &*NU_CACHE_DIR, + &*HUB_CACHE_DIR, + &*POWERSHELL_CACHE_DIR, + &*JAVA_CACHE_DIR, + &*RUBY_CACHE_DIR, + &*TAR_JAVA_CACHE_DIR, // for related places search: ADD_NEW_LANG ] { DirBuilder::new() .recursive(true) diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 17e79c3393..6baba34a02 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -73,7 +73,7 @@ use windmill_common::{ load_periodic_bash_script_interval_from_env, load_whitelist_env_vars_from_env, load_worker_config, reload_custom_tags_setting, store_pull_query, store_suspended_pull_query, Connection, WorkerConfig, DEFAULT_TAGS_PER_WORKSPACE, - DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, TMP_DIR, + DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, WINDMILL_DIR, WORKER_CONFIG, WORKER_GROUP, }, KillpillSender, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE, CRITICAL_ALERT_MUTE_UI_ENABLED, @@ -595,7 +595,7 @@ async fn sleep_until_next_minute_start_plus_one_s() { use windmill_common::tracing_init::TMP_WINDMILL_LOGS_SERVICE; async fn find_two_highest_files(hostname: &str) -> (Option, Option) { - let log_dir = format!("{}/{}/", TMP_WINDMILL_LOGS_SERVICE, hostname); + let log_dir = format!("{}/{}/", *TMP_WINDMILL_LOGS_SERVICE, hostname); let rd_dir = tokio::fs::read_dir(log_dir).await; if let Ok(mut log_files) = rd_dir { let mut highest_file: Option = None; @@ -614,7 +614,8 @@ async fn find_two_highest_files(hostname: &str) -> (Option, Option () { .iter() .map(|f| format!("{}/{}", f.hostname, f.file_path)) .collect(); - delete_log_files_from_disk_and_store(paths, TMP_WINDMILL_LOGS_SERVICE, windmill_common::tracing_init::LOGS_SERVICE).await; + delete_log_files_from_disk_and_store(paths, &*TMP_WINDMILL_LOGS_SERVICE, windmill_common::tracing_init::LOGS_SERVICE).await; } Err(e) => tracing::error!("Error deleting log file: {:?}", e), @@ -1140,7 +1141,7 @@ async fn delete_expired_jobs_batch( .filter_map(|opt| opt) .flat_map(|inner_vec| inner_vec.into_iter()) .collect(); - delete_log_files_from_disk_and_store(paths, TMP_DIR, "").await; + delete_log_files_from_disk_and_store(paths, &*WINDMILL_DIR, "").await; } Err(e) => tracing::error!("Error deleting job logs: {:?}", e), } @@ -1367,7 +1368,7 @@ pub async fn reload_maven_settings_xml_setting(conn: &Connection) { let settings_xml = MAVEN_SETTINGS_XML.read().await.clone(); match settings_xml { Some(ref content) if !content.trim().is_empty() => { - let m2_dir = format!("{JAVA_HOME_DIR}/.m2"); + let m2_dir = format!("{}/.m2", *JAVA_HOME_DIR); if let Err(e) = tokio::fs::create_dir_all(&m2_dir).await { tracing::error!("Failed to create .m2 directory: {e:#}"); return; @@ -1378,7 +1379,7 @@ pub async fn reload_maven_settings_xml_setting(conn: &Connection) { } } _ => { - let settings_path = format!("{JAVA_HOME_DIR}/.m2/settings.xml"); + let settings_path = format!("{}/.m2/settings.xml", *JAVA_HOME_DIR); let _ = tokio::fs::remove_file(&settings_path).await; } } diff --git a/backend/tests/end_user_email.rs b/backend/tests/end_user_email.rs new file mode 100644 index 0000000000..1a60f8672c --- /dev/null +++ b/backend/tests/end_user_email.rs @@ -0,0 +1,323 @@ +//! Tests for WM_END_USER_EMAIL environment variable. +//! +//! These tests verify that WM_END_USER_EMAIL is populated with the authenticated +//! user's email when executing app components. +//! +//! TODO: Add tests for scripts and flows once public execution endpoints are identified. +//! Currently only apps support non-workspace-member execution via OptAuthed + token lookup. + +use serde_json::json; +use sqlx::{Pool, Postgres}; +use windmill_common::worker::Connection; +use windmill_test_utils::*; + +const SAME_WS_TOKEN: &str = "SECRET_TOKEN"; +const OTHER_WS_TOKEN: &str = "OTHER_WS_TOKEN"; +const NO_WS_TOKEN: &str = "NO_WS_TOKEN"; + +const SAME_WS_EMAIL: &str = "test@windmill.dev"; +const OTHER_WS_EMAIL: &str = "other-ws@windmill.dev"; +const NO_WS_EMAIL: &str = "no-ws@windmill.dev"; + +fn client() -> reqwest::Client { + reqwest::Client::new() +} + +fn authed(builder: reqwest::RequestBuilder, token: &str) -> reqwest::RequestBuilder { + builder.header("Authorization", format!("Bearer {}", token)) +} + +// TODO: Script tests - need to identify public execution endpoints for non-workspace-members +// async fn run_script(port: u16, token: &str) -> anyhow::Result { +// let url = format!( +// "http://localhost:{}/api/w/test-workspace/jobs/run_wait_result/p/f/test/get_end_user_email", +// port +// ); +// let resp = authed(client().post(&url), token) +// .json(&json!({})) +// .send() +// .await?; +// if !resp.status().is_success() { +// anyhow::bail!("script run failed: {} - {}", resp.status(), resp.text().await?); +// } +// Ok(resp.json::().await? +// .as_str().unwrap_or("").to_string()) +// } + +// TODO: Flow tests - need to identify public execution endpoints for non-workspace-members +// async fn run_flow(port: u16, token: &str) -> anyhow::Result { +// let url = format!( +// "http://localhost:{}/api/w/test-workspace/jobs/run_wait_result/f/f/test/get_end_user_email_flow", +// port +// ); +// let resp = authed(client().post(&url), token) +// .json(&json!({})) +// .send() +// .await?; +// if !resp.status().is_success() { +// anyhow::bail!("flow run failed: {} - {}", resp.status(), resp.text().await?); +// } +// Ok(resp.json::().await? +// .as_str().unwrap_or("").to_string()) +// } + +/// Create an app with inline script via API +async fn create_app_with_inline_script(port: u16, path: &str) -> anyhow::Result<()> { + let url = format!( + "http://localhost:{}/api/w/test-workspace/apps/create", + port + ); + let resp = authed(client().post(&url), SAME_WS_TOKEN) + .json(&json!({ + "path": path, + "summary": "Test app for WM_END_USER_EMAIL", + "value": { + "type": "app", + "grid": [], + "subgrids": {}, + "hiddenInlineScripts": [{ + "name": "get_email", + "language": "deno", + "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }", + "path": "f/test/email_app/get_email" + }] + }, + "policy": { + "execution_mode": "anonymous", + "on_behalf_of": null, + "on_behalf_of_email": null, + "triggerables_v2": { + "get_email": { + "static_inputs": {}, + "one_of_inputs": {} + }, + // SHA256 hash of raw_code content for anonymous execution + "rawscript/6428aba5aa2d3ea8e1215bfdccbedd3718b18da7a239e3778a9787bb9a0ea606": { + "static_inputs": {}, + "one_of_inputs": {} + } + } + } + })) + .send() + .await?; + if !resp.status().is_success() { + anyhow::bail!("create app failed: {} - {}", resp.status(), resp.text().await?); + } + Ok(()) +} + +/// Create a raw app with inline script via API (uses regular app endpoint with rawapp type) +async fn create_raw_app_with_inline_script(port: u16, path: &str) -> anyhow::Result<()> { + let url = format!( + "http://localhost:{}/api/w/test-workspace/apps/create", + port + ); + let resp = authed(client().post(&url), SAME_WS_TOKEN) + .json(&json!({ + "path": path, + "summary": "Test raw app for WM_END_USER_EMAIL", + "value": { + "type": "rawapp", + "css": "", + "inlineScripts": [{ + "name": "get_email", + "language": "deno", + "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }" + }] + }, + "policy": { + "execution_mode": "anonymous", + "on_behalf_of": null, + "on_behalf_of_email": null, + "triggerables_v2": { + "get_email": { + "static_inputs": {}, + "one_of_inputs": {} + }, + // SHA256 hash of raw_code content for anonymous execution + "rawscript/6428aba5aa2d3ea8e1215bfdccbedd3718b18da7a239e3778a9787bb9a0ea606": { + "static_inputs": {}, + "one_of_inputs": {} + } + } + } + })) + .send() + .await?; + if !resp.status().is_success() { + anyhow::bail!("create raw app failed: {} - {}", resp.status(), resp.text().await?); + } + Ok(()) +} + +async fn run_app_inline_script(port: u16, token: &str, app_path: &str, force_viewer: bool) -> anyhow::Result { + let url = format!( + "http://localhost:{}/api/w/test-workspace/apps_u/execute_component/{}", + port, app_path + ); + let mut payload = json!({ + "args": {}, + "component": "get_email", + "raw_code": { + "language": "deno", + "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }", + "path": format!("{}/get_email", app_path) + } + }); + if force_viewer { + payload["force_viewer_static_fields"] = json!({}); + } + let resp = authed(client().post(&url), token) + .json(&payload) + .send() + .await?; + if !resp.status().is_success() { + anyhow::bail!("app inline script run failed: {} - {}", resp.status(), resp.text().await?); + } + let job_id = resp.text().await?; + wait_for_job_result(port, token, &job_id).await +} + +async fn run_raw_app_inline_script(port: u16, token: &str, app_path: &str, force_viewer: bool) -> anyhow::Result { + let url = format!( + "http://localhost:{}/api/w/test-workspace/apps_u/execute_component/{}", + port, app_path + ); + let mut payload = json!({ + "args": {}, + "component": "get_email", + "raw_code": { + "language": "deno", + "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }" + } + }); + if force_viewer { + payload["force_viewer_static_fields"] = json!({}); + } + let resp = authed(client().post(&url), token) + .json(&payload) + .send() + .await?; + if !resp.status().is_success() { + anyhow::bail!("raw app inline script run failed: {} - {}", resp.status(), resp.text().await?); + } + let job_id = resp.text().await?; + wait_for_job_result(port, token, &job_id).await +} + +async fn wait_for_job_result(port: u16, token: &str, job_id: &str) -> anyhow::Result { + let url = format!( + "http://localhost:{}/api/w/test-workspace/jobs_u/completed/get_result/{}", + port, job_id + ); + for _ in 0..100 { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + let resp = authed(client().get(&url), token).send().await?; + if resp.status().is_success() { + return Ok(resp.json::().await? + .as_str().unwrap_or("").to_string()); + } + } + anyhow::bail!("timeout waiting for job result") +} + +// TODO: Script tests - need to identify public execution endpoints for non-workspace-members +// #[cfg(feature = "deno_core")] +// #[sqlx::test(fixtures("base", "end_user_email"))] +// async fn test_script_wm_end_user_email(db: Pool) -> anyhow::Result<()> { +// initialize_tracing().await; +// set_jwt_secret().await; +// let server = ApiServer::start(db.clone()).await?; +// let port = server.addr.port(); +// +// in_test_worker(Connection::Sql(db.clone()), async move { +// let result = run_script(port, SAME_WS_TOKEN).await?; +// assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email"); +// Ok::<(), anyhow::Error>(()) +// }, port).await?; +// +// Ok(()) +// } + +// TODO: Flow tests - need to identify public execution endpoints for non-workspace-members +// #[cfg(feature = "deno_core")] +// #[sqlx::test(fixtures("base", "end_user_email"))] +// async fn test_flow_wm_end_user_email(db: Pool) -> anyhow::Result<()> { +// initialize_tracing().await; +// set_jwt_secret().await; +// let server = ApiServer::start(db.clone()).await?; +// let port = server.addr.port(); +// +// in_test_worker(Connection::Sql(db.clone()), async move { +// let result = run_flow(port, SAME_WS_TOKEN).await?; +// assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email"); +// Ok::<(), anyhow::Error>(()) +// }, port).await?; +// +// Ok(()) +// } + +#[cfg(feature = "deno_core")] +#[sqlx::test(fixtures("base", "end_user_email"))] +async fn test_app_wm_end_user_email(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + set_jwt_secret().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + let app_path = "f/test/email_app"; + + in_test_worker(Connection::Sql(db.clone()), async move { + // Create the app with inline script first + create_app_with_inline_script(port, app_path).await?; + + // Same workspace user (force_viewer mode works for workspace members) + let result = run_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?; + assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email"); + + // Other workspace user (uses app's anonymous policy + token lookup) + let result = run_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?; + assert_eq!(result, OTHER_WS_EMAIL, "other workspace user should get their email"); + + // No workspace user (uses app's anonymous policy + token lookup) + let result = run_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?; + assert_eq!(result, NO_WS_EMAIL, "no workspace user should get their email"); + + Ok::<(), anyhow::Error>(()) + }, port).await?; + + Ok(()) +} + +#[cfg(feature = "deno_core")] +#[sqlx::test(fixtures("base", "end_user_email"))] +async fn test_raw_app_wm_end_user_email(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + set_jwt_secret().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + let app_path = "f/test/email_raw_app"; + + in_test_worker(Connection::Sql(db.clone()), async move { + // Create the raw app with inline script first + create_raw_app_with_inline_script(port, app_path).await?; + + // Same workspace user (force_viewer mode works for workspace members) + let result = run_raw_app_inline_script(port, SAME_WS_TOKEN, app_path, true).await?; + assert_eq!(result, SAME_WS_EMAIL, "same workspace user should get their email"); + + // Other workspace user (uses app's anonymous policy + token lookup) + let result = run_raw_app_inline_script(port, OTHER_WS_TOKEN, app_path, false).await?; + assert_eq!(result, OTHER_WS_EMAIL, "other workspace user should get their email"); + + // No workspace user (uses app's anonymous policy + token lookup) + let result = run_raw_app_inline_script(port, NO_WS_TOKEN, app_path, false).await?; + assert_eq!(result, NO_WS_EMAIL, "no workspace user should get their email"); + + Ok::<(), anyhow::Error>(()) + }, port).await?; + + Ok(()) +} diff --git a/backend/tests/fixtures/end_user_email.sql b/backend/tests/fixtures/end_user_email.sql new file mode 100644 index 0000000000..654ad93680 --- /dev/null +++ b/backend/tests/fixtures/end_user_email.sql @@ -0,0 +1,63 @@ +-- Fixture for WM_END_USER_EMAIL tests +-- Sets up 3 users with different workspace memberships: +-- 1. test@windmill.dev - in test-workspace (from base.sql) +-- 2. other-ws@windmill.dev - in other-workspace only +-- 3. no-ws@windmill.dev - not in any workspace + +-- Second workspace for cross-workspace user +INSERT INTO workspace (id, name, owner) +VALUES ('other-workspace', 'other-workspace', 'other-ws-user'); + +INSERT INTO workspace_key(workspace_id, kind, key) +VALUES ('other-workspace', 'cloud', 'other-key'); + +INSERT INTO workspace_settings (workspace_id) +VALUES ('other-workspace'); + +INSERT INTO group_ (workspace_id, name, summary, extra_perms) +VALUES ('other-workspace', 'all', 'All users', '{}'); + +-- User in other-workspace only (not in test-workspace) +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) +VALUES ('other-ws@windmill.dev', 'hash', 'password', false, true, 'Other WS User'); + +INSERT INTO usr(workspace_id, email, username, is_admin, role) +VALUES ('other-workspace', 'other-ws@windmill.dev', 'other-ws-user', true, 'Admin'); + +INSERT INTO token(token, email, label, super_admin) +VALUES ('OTHER_WS_TOKEN', 'other-ws@windmill.dev', 'other ws token', false); + +-- User not in any workspace +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name) +VALUES ('no-ws@windmill.dev', 'hash', 'password', false, true, 'No WS User'); + +INSERT INTO token(token, email, label, super_admin) +VALUES ('NO_WS_TOKEN', 'no-ws@windmill.dev', 'no ws token', false); + +-- Script that returns WM_END_USER_EMAIL (public via extra_perms) +INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, extra_perms) +VALUES ( + 'test-workspace', 'test-user', + 'export function main() { return Deno.env.get("WM_END_USER_EMAIL") || ""; }', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + 'Returns WM_END_USER_EMAIL', '', 'f/test/get_end_user_email', 900001, 'deno', '', 'script', + '{"g/all": true}' +); + +-- Flow that returns WM_END_USER_EMAIL (public via extra_perms) +INSERT INTO flow (workspace_id, summary, description, path, versions, schema, value, edited_by, extra_perms) +VALUES ( + 'test-workspace', 'Returns WM_END_USER_EMAIL', '', 'f/test/get_end_user_email_flow', '{900002}', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + '{"modules": [{"id": "a", "value": {"type": "rawscript", "language": "deno", "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }", "input_transforms": {}}}]}', + 'test-user', + '{"g/all": true}' +); + +INSERT INTO flow_version (id, workspace_id, path, schema, value, created_by) +VALUES ( + 900002, 'test-workspace', 'f/test/get_end_user_email_flow', + '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', + '{"modules": [{"id": "a", "value": {"type": "rawscript", "language": "deno", "content": "export function main() { return Deno.env.get(\"WM_END_USER_EMAIL\") || \"\"; }", "input_transforms": {}}}]}', + 'test-user' +); diff --git a/backend/tests/nativets_stress.rs b/backend/tests/nativets_stress.rs index 9717403830..082a43be2c 100644 --- a/backend/tests/nativets_stress.rs +++ b/backend/tests/nativets_stress.rs @@ -206,7 +206,7 @@ fn spawn_workers( std::fs::DirBuilder::new() .recursive(true) - .create(windmill_worker::GO_BIN_CACHE_DIR) + .create(&*windmill_worker::GO_BIN_CACHE_DIR) .expect("could not create initial worker dir"); let (tx, _) = KillpillSender::new(n + 1); diff --git a/backend/tests/python_jobs.rs b/backend/tests/python_jobs.rs index dd494de007..22c2313b91 100644 --- a/backend/tests/python_jobs.rs +++ b/backend/tests/python_jobs.rs @@ -1,7 +1,7 @@ -use windmill_test_utils::*; use sqlx::postgres::Postgres; use sqlx::Pool; use windmill_common::scripts::ScriptLang; +use windmill_test_utils::*; #[cfg(feature = "python")] #[sqlx::test(fixtures("base", "lockfile_python"))] @@ -188,7 +188,8 @@ def main(): path: None, language: ScriptLang::Python3, lock: None, - concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(), + concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default() + .into(), debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(), cache_ttl: None, cache_ignore_s3_path: None, @@ -207,14 +208,14 @@ def main(): #[cfg(feature = "python")] #[sqlx::test(fixtures("base"))] async fn test_python_global_site_packages(db: Pool) -> anyhow::Result<()> { - use windmill_common::{cache::concatcp, worker::ROOT_CACHE_DIR}; + use windmill_common::worker::ROOT_CACHE_DIR; initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; let port = server.addr.port(); // Shared for all 3.12.* - let path = concatcp!(ROOT_CACHE_DIR, "python_3_12/global-site-packages").to_owned(); + let path = format!("{}python_3_12/global-site-packages", *ROOT_CACHE_DIR); std::fs::create_dir_all(&path).unwrap(); std::fs::write(path + "/my_global_site_package_3_12_any.py", "").unwrap(); @@ -237,7 +238,9 @@ def main(): path: None, language: ScriptLang::Python3, lock: None, - concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(), + concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default( + ) + .into(), debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(), cache_ttl: None, cache_ignore_s3_path: None, @@ -271,7 +274,9 @@ def main(): path: None, language: ScriptLang::Python3, lock: None, - concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(), + concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default( + ) + .into(), debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(), cache_ttl: None, cache_ignore_s3_path: None, @@ -310,7 +315,8 @@ def main(): path: None, language: ScriptLang::Python3, lock: None, - concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(), + concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default() + .into(), debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(), cache_ttl: None, cache_ignore_s3_path: None, @@ -347,7 +353,8 @@ def main(): path: None, language: ScriptLang::Python3, lock: None, - concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default().into(), + concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default() + .into(), debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(), cache_ttl: None, cache_ignore_s3_path: None, diff --git a/backend/windmill-api-auth/src/auth.rs b/backend/windmill-api-auth/src/auth.rs index 186f7af257..af212dc315 100644 --- a/backend/windmill-api-auth/src/auth.rs +++ b/backend/windmill-api-auth/src/auth.rs @@ -35,7 +35,45 @@ use windmill_common::{ lazy_static::lazy_static! { // Global auth cache accessible from main.rs for direct invalidation pub static ref AUTH_CACHE: Cache<(String, String), ExpiringAuthCache> = Cache::new(300); + // Cache for token -> email lookups (for non-workspace-member authenticated users) + static ref TOKEN_EMAIL_CACHE: Cache> = Cache::new(500); +} +/// Get email from a valid token, with caching. +/// Used for WM_END_USER_EMAIL when user is authenticated but not a workspace member. +async fn get_email_from_token(db: &DB, token: &str) -> Option { + if let Some(cached) = TOKEN_EMAIL_CACHE.get(token) { + return cached; + } + + let email = sqlx::query_scalar!( + "SELECT email FROM token WHERE token = $1 AND (expiration > NOW() OR expiration IS NULL)", + token + ) + .fetch_optional(db) + .await + .ok() + .flatten() + .flatten(); // email column is nullable, so we get Option> + + TOKEN_EMAIL_CACHE.insert(token.to_string(), email.clone()); + email +} + +/// Get end user email from authenticated user or token. +/// Returns email if user is authenticated (workspace member) or has valid instance token. +pub async fn get_end_user_email( + db: &DB, + opt_authed: Option<&ApiAuthed>, + token: Option<&str>, +) -> Option { + if let Some(authed) = opt_authed { + return Some(authed.email.clone()); + } + if let Some(token) = token { + return get_email_from_token(db, token).await; + } + None } // Global function to invalidate a specific token from cache pub fn invalidate_token_from_cache(token: &str) { diff --git a/backend/windmill-api-auth/src/lib.rs b/backend/windmill-api-auth/src/lib.rs index efab57cf91..5acb696bd4 100644 --- a/backend/windmill-api-auth/src/lib.rs +++ b/backend/windmill-api-auth/src/lib.rs @@ -29,8 +29,8 @@ use scopes::ScopeDefinition; // Re-export key auth types and functions pub use auth::{ - invalidate_token_from_cache, AuthCache, ExpiringAuthCache, OptTokened, Tokened, - TruncatedTokenWithEmail, AUTH_CACHE, + get_end_user_email, invalidate_token_from_cache, AuthCache, ExpiringAuthCache, OptTokened, + Tokened, TruncatedTokenWithEmail, AUTH_CACHE, }; // ------------ ApiAuthed & OptJobAuthed types ------------ diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs index 04a1eb617b..bf493f9420 100644 --- a/backend/windmill-api-settings/src/lib.rs +++ b/backend/windmill-api-settings/src/lib.rs @@ -1085,7 +1085,7 @@ async fn sync_cached_resource_types( require_super_admin(&db, &authed.email).await?; use windmill_common::worker::HUB_RT_CACHE_DIR; - let cache_path = format!("{}/resource_types.json", HUB_RT_CACHE_DIR); + let cache_path = format!("{}/resource_types.json", *HUB_RT_CACHE_DIR); let content = tokio::fs::read_to_string(&cache_path).await.map_err(|e| { error::Error::NotFound(format!( diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 4dc1d252c4..163217862e 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.648.0 + version: 1.649.0 title: Windmill API contact: diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index d156dc9c88..1cfbfa6e19 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -8,7 +8,7 @@ use std::{collections::HashMap, sync::Arc}; * LICENSE-AGPL for a copy of the license. */ use crate::{ - auth::OptTokened, + auth::{get_end_user_email, OptTokened}, db::{ApiAuthed, DB}, jobs::RunJobQuery, users::{require_owner_of_path, OptAuthed}, @@ -2149,7 +2149,7 @@ async fn execute_component( (email.as_str(), permissioned_as) }; - let end_user_email = opt_authed.as_ref().map(|a| a.email.clone()); + let end_user_email = get_end_user_email(&db, opt_authed.as_ref(), tokened.token.as_deref()).await; let (uuid, mut tx) = push( &db, diff --git a/backend/windmill-api/src/auth.rs b/backend/windmill-api/src/auth.rs index 144703ba5c..66a67c5f97 100644 --- a/backend/windmill-api/src/auth.rs +++ b/backend/windmill-api/src/auth.rs @@ -1,4 +1,5 @@ pub use windmill_api_auth::auth::{ - invalidate_token_from_cache, list_tokens_internal, transform_old_scope_to_new_scope, AuthCache, - ExpiringAuthCache, OptTokened, Tokened, TruncatedTokenWithEmail, + get_end_user_email, invalidate_token_from_cache, list_tokens_internal, + transform_old_scope_to_new_scope, AuthCache, ExpiringAuthCache, OptTokened, Tokened, + TruncatedTokenWithEmail, }; diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 420bbf24b9..d58dcd3c69 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -47,7 +47,7 @@ use windmill_common::runtime_assets::{register_runtime_asset, InsertRuntimeAsset use windmill_common::scripts::ScriptRunnableSettingsInline; use windmill_common::triggers::TriggerMetadata; use windmill_common::utils::{RunnableKind, WarnAfterExt}; -use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR}; +use windmill_common::worker::{Connection, CLOUD_HOSTED, WINDMILL_DIR}; use windmill_common::workspace_dependencies::{ RawWorkspaceDependencies, MIN_VERSION_WORKSPACE_DEPENDENCIES, }; @@ -1412,7 +1412,7 @@ async fn get_logs_from_disk( if log_offset > 0 { if let Some(file_index) = log_file_index.clone() { for file_p in &file_index { - if !tokio::fs::metadata(format!("{TMP_DIR}/{file_p}")) + if !tokio::fs::metadata(format!("{}/{file_p}", *WINDMILL_DIR)) .await .is_ok() { @@ -1427,7 +1427,7 @@ async fn get_logs_from_disk( "#.to_string(), )); for file_p in file_index.clone() { - let mut file = tokio::fs::File::open(format!("{TMP_DIR}/{file_p}")).await.map_err(to_anyhow)?; + let mut file = tokio::fs::File::open(format!("{}/{file_p}", *WINDMILL_DIR)).await.map_err(to_anyhow)?; let mut buffer = Vec::new(); file.read_to_end(&mut buffer).await.map_err(to_anyhow)?; yield Ok(bytes::Bytes::from(buffer)) as anyhow::Result; @@ -5888,7 +5888,7 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R )); } - let local_file = format!("{TMP_DIR}/logs/{file_p}"); + let local_file = format!("{}/logs/{file_p}", *WINDMILL_DIR); if tokio::fs::metadata(&local_file).await.is_ok() { let mut file = tokio::fs::File::open(local_file).await.map_err(to_anyhow)?; let mut buffer = Vec::new(); @@ -5934,10 +5934,10 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R } #[cfg(not(all(feature = "enterprise", feature = "parquet")))] - return Err(error::Error::NotFound(format!( - "File not found on server logs volume /tmp/windmill/logs and no distributed logs s3 storage for {}", - file_p - ))); + return Err(error::Error::NotFound(format!( + "File not found on server logs volume {}/logs and no distributed logs s3 storage for {}", + *WINDMILL_DIR, file_p + ))); } async fn get_job_update( diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 9b713cad0a..528e60b02f 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -262,7 +262,7 @@ pub async fn run_server( ) -> anyhow::Result<()> { let user_db = UserDB::new(db.clone()); - for x in [HUB_CACHE_DIR] { + for x in [&*HUB_CACHE_DIR] { DirBuilder::new() .recursive(true) .create(x) diff --git a/backend/windmill-api/src/service_logs.rs b/backend/windmill-api/src/service_logs.rs index 20d2b58d0e..c83bb21f2c 100644 --- a/backend/windmill-api/src/service_logs.rs +++ b/backend/windmill-api/src/service_logs.rs @@ -102,7 +102,11 @@ async fn get_log_file( #[cfg(feature = "parquet")] if let Some(s3_client) = s3_client { let path = format!("{}{}", windmill_common::tracing_init::LOGS_SERVICE, path); - let file = s3_client.get(&windmill_object_store::object_store_reexports::Path::from(path)).await; + let file = s3_client + .get(&windmill_object_store::object_store_reexports::Path::from( + path, + )) + .await; match file { Ok(file) => { let bytes = file.bytes().await; @@ -126,7 +130,7 @@ async fn get_log_file( } } } - let file = tokio::fs::read(format!("{}{}", TMP_WINDMILL_LOGS_SERVICE, path)).await; + let file = tokio::fs::read(format!("{}{}", *TMP_WINDMILL_LOGS_SERVICE, path)).await; if let Ok(bytes) = file { Ok(content_plain(Body::from(bytes::Bytes::from(bytes)))) } else { diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index c881e9ae9b..091c465a61 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -43,6 +43,7 @@ use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings use windmill_common::scripts::ScriptRunnableSettingsHandle; use windmill_common::utils::require_admin; use windmill_common::variables::decrypt; +use windmill_common::worker::WINDMILL_DIR; use windmill_common::{ db::UserDB, error::{to_anyhow, Error, Result}, @@ -372,7 +373,7 @@ pub(crate) async fn tarball_workspace( let mut tx = user_db.begin(&authed).await?; - let tmp_dir = TempDir::new_in("/tmp/windmill/")?; + let tmp_dir = TempDir::new_in(&*WINDMILL_DIR)?; let name = match archive_type.as_deref() { Some("tar") | None => Ok(format!("windmill-{w_id}.tar")), diff --git a/backend/windmill-common/src/bench.rs b/backend/windmill-common/src/bench.rs index b87872119b..1ed2b9aa56 100644 --- a/backend/windmill-common/src/bench.rs +++ b/backend/windmill-common/src/bench.rs @@ -1,5 +1,5 @@ use crate::{ - worker::{write_file, TMP_DIR}, + worker::{write_file, WINDMILL_DIR}, DB, }; use serde::Serialize; @@ -113,7 +113,8 @@ impl BenchmarkInfo { "Writing benchmark {path}, duration of benchmark: {total_duration}ms and RPS: {}{pool_info}", self.iters as f64 / total_duration as f64 * 1000.0 ); - write_file(TMP_DIR, path, &serde_json::to_string(&self).unwrap()).expect("write profiling"); + write_file(&WINDMILL_DIR, path, &serde_json::to_string(&self).unwrap()) + .expect("write profiling"); Ok(()) } } diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index bc40e4cd75..583914c573 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -18,7 +18,7 @@ use crate::{ scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang}, users::username_to_permissioned_as, utils::{StripPath, HTTP_CLIENT}, - worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE, TMP_DIR}, + worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE, WINDMILL_DIR}, FlowVersionInfo, ScriptHashInfo, Tag, }; @@ -225,7 +225,7 @@ pub async fn get_logs_from_disk( if log_offset > 0 { if let Some(file_index) = log_file_index.clone() { for file_p in &file_index { - if !tokio::fs::metadata(format!("{TMP_DIR}/{file_p}")) + if !tokio::fs::metadata(format!("{}/{file_p}", *WINDMILL_DIR)) .await .is_ok() { @@ -236,7 +236,7 @@ pub async fn get_logs_from_disk( let logs = logs.to_string(); let stream = async_stream::stream! { for file_p in file_index.clone() { - let mut file = tokio::fs::File::open(format!("{TMP_DIR}/{file_p}")).await.map_err(to_anyhow)?; + let mut file = tokio::fs::File::open(format!("{}/{file_p}", *WINDMILL_DIR)).await.map_err(to_anyhow)?; let mut buffer = Vec::new(); file.read_to_end(&mut buffer).await.map_err(to_anyhow)?; yield Ok(bytes::Bytes::from(buffer)) as anyhow::Result; diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 68f77a2303..cfff28dbcb 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -206,12 +206,12 @@ pub async fn get_full_hub_script_by_path( let version = path_iterator .next() .ok_or_else(|| Error::internal_err(format!("expected hub path to have version number")))?; - let cache_path = format!("{HUB_CACHE_DIR}/{version}"); + let cache_path = format!("{}/{version}", *HUB_CACHE_DIR); let script; if tokio::fs::metadata(&cache_path).await.is_err() { script = get_full_hub_script_by_path_inner(path, http_client, db).await?; if let Err(e) = crate::worker::write_file( - HUB_CACHE_DIR, + &HUB_CACHE_DIR, &version, &serde_json::to_string(&script).map_err(to_anyhow)?, ) { diff --git a/backend/windmill-common/src/tracing_init.rs b/backend/windmill-common/src/tracing_init.rs index 701a0887d0..f4892b67c9 100644 --- a/backend/windmill-common/src/tracing_init.rs +++ b/backend/windmill-common/src/tracing_init.rs @@ -6,8 +6,6 @@ * LICENSE-AGPL for a copy of the license. */ -use const_format::concatcp; - use std::{ collections::HashMap, sync::{Arc, RwLock}, @@ -61,7 +59,9 @@ fn create_targets_filter(default_env_filter: LevelFilter) -> Targets { pub const LOGS_SERVICE: &str = "logs/services/"; -pub const TMP_WINDMILL_LOGS_SERVICE: &str = concatcp!("/tmp/windmill/", LOGS_SERVICE); +lazy_static::lazy_static! { + pub static ref TMP_WINDMILL_LOGS_SERVICE: String = format!("{}/{}", *crate::worker::WINDMILL_DIR, LOGS_SERVICE); +} pub fn initialize_tracing( hostname: &str, @@ -108,7 +108,7 @@ pub fn initialize_tracing( use tracing_appender::rolling::{RollingFileAppender, Rotation}; - let log_dir = format!("{}/{}/", TMP_WINDMILL_LOGS_SERVICE, hostname); + let log_dir = format!("{}/{}/", *TMP_WINDMILL_LOGS_SERVICE, hostname); std::fs::create_dir_all(&log_dir).unwrap(); let file_appender = RollingFileAppender::builder() .rotation(Rotation::MINUTELY) diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index 1a2d55b896..8d95541f61 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -2,7 +2,6 @@ use anyhow::anyhow; use axum::http::HeaderMap; use bytes::Bytes; -use const_format::concatcp; use itertools::Itertools; use regex::Regex; use reqwest_middleware::ClientWithMiddleware; @@ -274,7 +273,9 @@ lazy_static::lazy_static! { pub static ref ROOT_STANDALONE_BUNDLE_DIR: String = format!("{}/.windmill/standalone_bundle", std::env::var("HOME").unwrap_or_else(|_| "/root".to_string())); } -pub const ROOT_CACHE_NOMOUNT_DIR: &str = concatcp!(TMP_DIR, "/cache_nomount/"); +lazy_static::lazy_static! { + pub static ref ROOT_CACHE_NOMOUNT_DIR: String = format!("{}/cache_nomount/", *WINDMILL_DIR); +} /// Whether native mode is forced by the environment (NATIVE_MODE=true env var or WORKER_GROUP=native). /// This does NOT account for native_mode set in the DB worker group config — for that, read @@ -490,13 +491,23 @@ pub async fn store_pull_query(wc: &WorkerConfig) { *l = queries; } -pub const TMP_DIR: &str = "/tmp/windmill"; -pub const TMP_LOGS_DIR: &str = concatcp!(TMP_DIR, "/logs"); - -pub const HUB_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "hub"); -pub const HUB_RT_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "hub_rt"); - -pub const ROOT_CACHE_DIR: &str = concatcp!(TMP_DIR, "/cache/"); +lazy_static::lazy_static! { + pub static ref WINDMILL_DIR: String = { + let dir = std::env::var("WINDMILL_DIR") + .unwrap_or_else(|_| "/tmp/windmill".to_string()); + if dir.is_empty() { + panic!("WINDMILL_DIR must not be empty"); + } + if dir.ends_with('/') || dir.ends_with('\\') { + panic!("WINDMILL_DIR must not end with a trailing slash, got: {dir}"); + } + dir + }; + pub static ref TMP_LOGS_DIR: String = format!("{}/logs", *WINDMILL_DIR); + pub static ref ROOT_CACHE_DIR: String = format!("{}/cache/", *WINDMILL_DIR); + pub static ref HUB_CACHE_DIR: String = format!("{}hub", *ROOT_CACHE_DIR); + pub static ref HUB_RT_CACHE_DIR: String = format!("{}hub_rt", *ROOT_CACHE_DIR); +} pub fn write_file(dir: &str, path: &str, content: &str) -> error::Result { let path = format!("{}/{}", dir, path); diff --git a/backend/windmill-duckdb-ffi-internal/Cargo.lock b/backend/windmill-duckdb-ffi-internal/Cargo.lock index 07e428c633..559196a3c2 100644 --- a/backend/windmill-duckdb-ffi-internal/Cargo.lock +++ b/backend/windmill-duckdb-ffi-internal/Cargo.lock @@ -2164,6 +2164,7 @@ version = "0.1.0" dependencies = [ "chrono", "duckdb", + "regex", "rust_decimal", "serde", "serde_json", diff --git a/backend/windmill-duckdb-ffi-internal/Cargo.toml b/backend/windmill-duckdb-ffi-internal/Cargo.toml index 7043b33ee5..7eb6869ab9 100644 --- a/backend/windmill-duckdb-ffi-internal/Cargo.toml +++ b/backend/windmill-duckdb-ffi-internal/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] chrono = "0.4.41" duckdb = { version = "1.4.4", features = ["bundled"] } +regex = "1" rust_decimal = "1.37.2" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "^1", features = ["preserve_order", "raw_value"] } diff --git a/backend/windmill-duckdb-ffi-internal/src/lib.rs b/backend/windmill-duckdb-ffi-internal/src/lib.rs index c5c819e60b..2701319d6e 100644 --- a/backend/windmill-duckdb-ffi-internal/src/lib.rs +++ b/backend/windmill-duckdb-ffi-internal/src/lib.rs @@ -1,12 +1,14 @@ use std::{ collections::HashMap, - ffi::{CStr, CString, c_char, c_uint}, + ffi::{c_char, c_uint, CStr, CString}, ptr::null_mut, + sync::LazyLock, }; -use duckdb::{Row, core::LogicalTypeId, params_from_iter, types::TimeUnit}; -use rust_decimal::{Decimal, prelude::FromPrimitive}; -use serde::Deserialize; +use duckdb::{core::LogicalTypeId, params_from_iter, types::TimeUnit, Row}; +use regex::Regex; +use rust_decimal::{prelude::FromPrimitive, Decimal}; +use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; #[derive(Deserialize, Clone, Debug, PartialEq, Default)] @@ -96,6 +98,218 @@ pub extern "C" fn run_duckdb_ffi( }) } +#[derive(Serialize, Debug)] +struct PrepareQueryColumnInfo { + name: String, + #[serde(rename = "type")] + type_name: String, +} + +#[derive(Serialize, Debug)] +struct PrepareQueryResult { + #[serde(skip_serializing_if = "Option::is_none")] + columns: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + error: Option, +} + +fn is_setup_statement(query: &str) -> bool { + let trimmed = query.trim_start(); + let upper = trimmed.to_uppercase(); + upper.starts_with("ATTACH") + || upper.starts_with("USE") + || upper.starts_with("INSTALL") + || upper.starts_with("LOAD") + || upper.starts_with("SET") + || upper.starts_with("RESET") + || upper.starts_with("CREATE OR REPLACE SECRET") + || upper.starts_with("CREATE SECRET") +} + +/// Returns true if the query is expected to return a result set and can be wrapped with DESCRIBE. +fn is_describable_query(query: &str) -> bool { + let trimmed = query.trim_start(); + let upper = trimmed.to_uppercase(); + upper.starts_with("SELECT") + || upper.starts_with("WITH") + || upper.starts_with("VALUES") + || upper.starts_with("TABLE") + || upper.starts_with("FROM") +} + +static PARAM_RE: LazyLock = LazyLock::new(|| Regex::new(r"\$\d+").expect("invalid regex")); + +fn replace_params_with_null(query: &str) -> String { + PARAM_RE.replace_all(query, "NULL").to_string() +} + +#[unsafe(no_mangle)] +pub extern "C" fn prepare_duckdb_ffi( + query_block_list: *const *const c_char, + query_block_list_count: usize, + token: *const c_char, + base_internal_url: *const c_char, + w_id: *const c_char, +) -> *mut c_char { + let r = match convert_prepare_args( + query_block_list, + query_block_list_count, + token, + base_internal_url, + w_id, + ) + .and_then(|(query_block_list, token, base_internal_url, w_id)| { + prepare_duckdb_internal(query_block_list, token, base_internal_url, w_id) + }) { + Ok(result) => result, + Err(err) => { + let err = serde_json::to_string(&err) + .unwrap_or_else(|_| "Unknown error in duckdb ffi lib".to_string()); + format!("ERROR {}", err) + } + }; + + CString::new(r).map(|s| s.into_raw()).unwrap_or_else(|e| { + println!("Failed to allocate error string in duckdb ffi lib: {:?}", e); + null_mut() + }) +} + +fn setup_duckdb_connection( + conn: &duckdb::Connection, + token: &str, + base_internal_url: &str, + w_id: &str, +) -> Result<(), String> { + let (s3_access_key, s3_secret_key) = token.rsplit_once('.').unwrap_or(("", token)); + let (s3_endpoint_ssl, s3_endpoint) = base_internal_url + .split_once("://") + .unwrap_or(("http", &base_internal_url)); + let s3_endpoint_ssl = s3_endpoint_ssl == "https"; + + conn.execute_batch(&format!( + "INSTALL httpfs; LOAD httpfs; + INSTALL azure; LOAD azure; + CREATE OR REPLACE SECRET s3_secret ( + TYPE s3, + PROVIDER config, + KEY_ID '{s3_access_key}', + SECRET '{s3_secret_key}', + ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy', + URL_STYLE path, + USE_SSL {s3_endpoint_ssl} + ); + CREATE OR REPLACE SECRET gcs_secret ( + TYPE gcs, + KEY_ID '{s3_access_key}', + SECRET '{s3_secret_key}', + ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy', + USE_SSL {s3_endpoint_ssl} + ); + ", + )) + .map_err(|e| format!("Error setting up S3 secret: {}", e.to_string())) +} + +fn convert_prepare_args<'a>( + query_block_list: *const *const c_char, + query_block_list_count: usize, + token: *const c_char, + base_internal_url: *const c_char, + w_id: *const c_char, +) -> Result<(Vec<&'a str>, &'a str, &'a str, &'a str), String> { + let query_block_list = unsafe { + std::slice::from_raw_parts(query_block_list, query_block_list_count) + .iter() + .map(|q| { + CStr::from_ptr(*q).to_str().unwrap_or_else(|e| { + println!( + "Invalid query_block string pointer in duckdb ffi: {}", + e.to_string() + ); + "Invalid query_block string pointer in duckdb ffi" + }) + }) + .collect::>() + }; + let token = unsafe { CStr::from_ptr(token) } + .to_str() + .map_err(|e| format!("Invalid token string: {}", e.to_string()))?; + let base_internal_url = unsafe { CStr::from_ptr(base_internal_url) } + .to_str() + .map_err(|e| format!("Invalid base_internal_url string: {}", e.to_string()))?; + let w_id = unsafe { CStr::from_ptr(w_id) } + .to_str() + .map_err(|e| format!("Invalid w_id string: {}", e.to_string()))?; + Ok((query_block_list, token, base_internal_url, w_id)) +} + +fn prepare_duckdb_internal( + query_block_list: Vec<&str>, + token: &str, + base_internal_url: &str, + w_id: &str, +) -> Result { + let conn = duckdb::Connection::open_in_memory().map_err(|e| e.to_string())?; + + setup_duckdb_connection(&conn, token, base_internal_url, w_id)?; + + let mut results: Vec = vec![]; + + // IMPORTANT: Setup statements (ATTACH, USE, INSTALL, etc.) are executed but intentionally + // do not produce a PrepareQueryResult entry. The frontend prepends these as connection setup + // before the actual user queries, and mapPrepareResults expects results.length to equal the + // number of user queries (not setup statements). If a new setup-like statement is added to + // the connection flow (e.g. in setup_duckdb_connection or transform_attach_ducklake) without + // also being caught by is_setup_statement, the result count will mismatch and the frontend + // will throw. + for query_block in &query_block_list { + if is_setup_statement(query_block) { + conn.execute_batch(query_block) + .map_err(|e| format!("Error executing setup statement: {}", e.to_string()))?; + continue; + } + + let modified_query = replace_params_with_null(query_block); + // Validate the query parses correctly by preparing it + if let Err(e) = conn.prepare(&modified_query) { + results.push(PrepareQueryResult { columns: None, error: Some(e.to_string()) }); + continue; + } + + // DESCRIBE only works on queries that return result sets (SELECT, WITH, VALUES, TABLE, + // FROM). For non-returning statements (INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.) + // we skip DESCRIBE and assume no columns. + if !is_describable_query(&modified_query) { + results.push(PrepareQueryResult { columns: Some(vec![]), error: None }); + continue; + } + + // Note: We have to use a DESCRIBE statement and cannot simply use the + // methods returned by .prepare() because they panic if the statement was + // not executed at least once (which we specifically do not want to do). + let describe_query = format!("DESCRIBE {}", modified_query); + match conn.prepare(&describe_query).and_then(|mut stmt| { + let rows = stmt.query_map([], |row| { + Ok(PrepareQueryColumnInfo { + name: row.get::<_, String>(0)?, + type_name: row.get::<_, String>(1)?, + }) + })?; + rows.collect::, _>>() + }) { + Ok(columns) => { + results.push(PrepareQueryResult { columns: Some(columns), error: None }); + } + Err(e) => { + results.push(PrepareQueryResult { columns: None, error: Some(e.to_string()) }); + } + } + } + + serde_json::to_string(&results).map_err(|e| e.to_string()) +} + fn convert_args<'a>( query_block_list: *const *const c_char, query_block_list_count: usize, @@ -170,38 +384,7 @@ fn run_duckdb_internal<'a>( ) -> Result<(String, Option>), String> { let conn = duckdb::Connection::open_in_memory().map_err(|e| e.to_string())?; - let (s3_access_key, s3_secret_key) = token.split_at(token.rfind('.').unwrap_or(0)); - let s3_secret_key = &s3_secret_key[1..]; - let (s3_endpoint_ssl, s3_endpoint) = base_internal_url - .split_once("://") - .unwrap_or(("http", &base_internal_url)); - let s3_endpoint_ssl = match s3_endpoint_ssl { - "https" => true, - _ => false, - }; - - conn.execute_batch(&format!( - "INSTALL httpfs; LOAD httpfs; - INSTALL azure; LOAD azure; - CREATE OR REPLACE SECRET s3_secret ( - TYPE s3, - PROVIDER config, - KEY_ID '{s3_access_key}', - SECRET '{s3_secret_key}', - ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy', - URL_STYLE path, - USE_SSL {s3_endpoint_ssl} - ); - CREATE OR REPLACE SECRET gcs_secret ( - TYPE gcs, - KEY_ID '{s3_access_key}', - SECRET '{s3_secret_key}', - ENDPOINT '{s3_endpoint}/api/w/{w_id}/s3_proxy', - USE_SSL {s3_endpoint_ssl} - ); - ", - )) - .map_err(|e| format!("Error setting up S3 secret: {}", e.to_string()))?; + setup_duckdb_connection(&conn, token, base_internal_url, w_id)?; let mut results: Vec>> = vec![]; let mut column_order = None; diff --git a/backend/windmill-runtime-nativets/src/lib.rs b/backend/windmill-runtime-nativets/src/lib.rs index a523106cbc..e81925e7bb 100644 --- a/backend/windmill-runtime-nativets/src/lib.rs +++ b/backend/windmill-runtime-nativets/src/lib.rs @@ -45,7 +45,7 @@ use uuid::Uuid; use windmill_common::error::Error; use windmill_common::result_stream::append_result_stream_db; -use windmill_common::worker::{write_file, Connection, TMP_DIR}; +use windmill_common::worker::{write_file, Connection, WINDMILL_DIR}; // ── Permission container ───────────────────────────────────────────── @@ -151,7 +151,9 @@ static RUNTIME_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/FETCH pub(crate) const WINDMILL_CLIENT: &str = include_str!("./windmill-client.js"); -const ERROR_DIR: &str = const_format::concatcp!(TMP_DIR, "/native_errors"); +lazy_static::lazy_static! { + static ref ERROR_DIR: String = format!("{}/native_errors", *WINDMILL_DIR); +} lazy_static! { static ref RE_PROXY: Regex = @@ -263,14 +265,14 @@ fn capture_proxy(s: &str) -> Option<(String, Option<(String, String)>)> { } fn write_error_expr(expr: &str, uuid: &Uuid) { - if let Err(e) = std::fs::create_dir_all(ERROR_DIR) { - tracing::error!("failed to create error dir {ERROR_DIR}: {e}"); + if let Err(e) = std::fs::create_dir_all(&*ERROR_DIR) { + tracing::error!("failed to create error dir {}: {e}", *ERROR_DIR); return; } - let dir_entries = match std::fs::read_dir(ERROR_DIR) { + let dir_entries = match std::fs::read_dir(&*ERROR_DIR) { Ok(entries) => entries.count(), Err(_) => { - tracing::error!("failed to read error dir {ERROR_DIR}"); + tracing::error!("failed to read error dir {}", *ERROR_DIR); return; } }; @@ -279,15 +281,16 @@ fn write_error_expr(expr: &str, uuid: &Uuid) { tracing::info!("native error for job {uuid}: {expr}"); } if dir_entries >= 100 { - tracing::info!("Too many error files in {ERROR_DIR}, skipping write"); + tracing::info!("Too many error files in {}, skipping write", *ERROR_DIR); return; } let path = format!("/{uuid}.js"); tracing::info!( - "nativets job {uuid} failed, writing error expr to {ERROR_DIR}/{path} for debugging: {path}" + "nativets job {uuid} failed, writing error expr to {}/{path} for debugging: {path}", + *ERROR_DIR ); - if let Err(e) = write_file(ERROR_DIR, &path, expr) { + if let Err(e) = write_file(&ERROR_DIR, &path, expr) { tracing::error!("failed to write error expr to file {path}: {e}"); } } diff --git a/backend/windmill-store/src/resources.rs b/backend/windmill-store/src/resources.rs index bcac53e7d2..6b414ca10f 100644 --- a/backend/windmill-store/src/resources.rs +++ b/backend/windmill-store/src/resources.rs @@ -47,7 +47,7 @@ use windmill_common::{ StripPath, }, variables, - worker::{CLOUD_HOSTED, TMP_DIR}, + worker::{CLOUD_HOSTED, WINDMILL_DIR}, PgDatabase, }; @@ -1752,7 +1752,7 @@ async fn write_ssh_file( var_path: &str, ) -> std::result::Result { let id_file_name = format!(".ssh_id_priv_{}", Uuid::new_v4()); - let loc = std::path::Path::new(TMP_DIR) + let loc = std::path::Path::new(&*WINDMILL_DIR) .join("ssh_ids") .join(id_file_name); diff --git a/backend/windmill-test-utils/src/lib.rs b/backend/windmill-test-utils/src/lib.rs index adbeb83ee7..69e7fc0558 100644 --- a/backend/windmill-test-utils/src/lib.rs +++ b/backend/windmill-test-utils/src/lib.rs @@ -380,7 +380,7 @@ pub fn spawn_test_worker( std::fs::DirBuilder::new() .recursive(true) - .create(windmill_worker::GO_BIN_CACHE_DIR) + .create(&*windmill_worker::GO_BIN_CACHE_DIR) .expect("could not create initial worker dir"); let (tx, rx) = KillpillSender::new(1); diff --git a/backend/windmill-worker/nsjail/run.powershell.config.proto b/backend/windmill-worker/nsjail/run.powershell.config.proto index 87b6abda21..afe9d5df9f 100644 --- a/backend/windmill-worker/nsjail/run.powershell.config.proto +++ b/backend/windmill-worker/nsjail/run.powershell.config.proto @@ -127,7 +127,7 @@ iface_no_lo: true mount { src: "{CACHE_DIR}" - dst: "/tmp/windmill/cache/powershell" + dst: "{CACHE_DIR}" is_bind: true rw: false mandatory: false diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index aa250de4ab..253384f6f2 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -33,9 +33,9 @@ use crate::{ read_and_check_result, start_child_process, transform_json, OccupancyMetrics, }, handle_child::handle_child, + is_sandboxing_enabled, python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile}, - is_sandboxing_enabled, DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, - PY_INSTALL_DIR, TZ_ENV, + DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV, }; use windmill_common::client::AuthedClient; @@ -1184,7 +1184,7 @@ mount {{ job_dir, "run.config.proto", &NSJAIL_CONFIG_RUN_ANSIBLE_CONTENT - .replace("{PY_INSTALL_DIR}", PY_INSTALL_DIR) + .replace("{PY_INSTALL_DIR}", &*PY_INSTALL_DIR) .replace("{JOB_DIR}", job_dir) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) .replace("{SHARED_MOUNT}", shared_mount) diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 64270b4044..30348895cf 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -194,7 +194,7 @@ exit $exit_status .replace("{JOB_DIR}", job_dir) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) .replace("{SHARED_MOUNT}", shared_mount) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL), )?; let mut cmd_args = vec![ diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 9c46db572d..08a61645ce 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -737,7 +737,7 @@ async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result Error { pub async fn clean_cache() -> error::Result<()> { tracing::info!("Started cleaning cache"); - tokio::fs::remove_dir_all(ROOT_CACHE_DIR).await?; + tokio::fs::remove_dir_all(&*ROOT_CACHE_DIR).await?; tracing::info!("Finished cleaning cache"); Ok(()) } @@ -1557,4 +1557,3 @@ mod tests { assert!(result.is_err()); } } - diff --git a/backend/windmill-worker/src/csharp_executor.rs b/backend/windmill-worker/src/csharp_executor.rs index e4c32648d0..4c3e63f4c9 100644 --- a/backend/windmill-worker/src/csharp_executor.rs +++ b/backend/windmill-worker/src/csharp_executor.rs @@ -13,14 +13,11 @@ use itertools::Itertools; #[cfg(feature = "csharp")] use tokio::{fs::File, io::AsyncReadExt, process::Command}; #[cfg(feature = "csharp")] -use windmill_common::{ - utils::calculate_hash, - worker::write_file, -}; +use windmill_common::{utils::calculate_hash, worker::write_file}; -use windmill_common::error::{self, Error}; #[cfg(feature = "csharp")] use crate::global_cache::save_cache; +use windmill_common::error::{self, Error}; #[cfg(feature = "csharp")] use windmill_queue::append_logs; @@ -105,8 +102,8 @@ pub async fn generate_nuget_lockfile( let mut gen_lockfile_cmd = Command::new(DOTNET_PATH.as_str()); gen_lockfile_cmd .current_dir(job_dir) - .env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR) - .env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget")) + .env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR) + .env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR)) .env("DOTNET_CLI_TELEMETRY_OPTOUT", "true") .env("DOTNET_NOLOGO", "true") .env("MSBUILDDISABLENODEREUSE", "1") @@ -367,8 +364,8 @@ async fn build_cs_proj( .env("PATH", PATH_ENV.as_str()) .env("BASE_INTERNAL_URL", base_internal_url) .env("HOME", HOME_ENV.as_str()) - .env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR) - .env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget")) + .env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR) + .env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR)) .env("DOTNET_CLI_TELEMETRY_OPTOUT", "true") .env("DOTNET_NOLOGO", "true") .env("MSBUILDDISABLENODEREUSE", "1") @@ -434,7 +431,7 @@ async fn build_cs_proj( } } - let bin_path = format!("{}/{hash}", CSHARP_CACHE_DIR); + let bin_path = format!("{}/{hash}", *CSHARP_CACHE_DIR); #[cfg(unix)] let target = format!("{job_dir}/Main"); #[cfg(windows)] @@ -516,11 +513,10 @@ pub async fn handle_csharp_job( inner_content, requirements_o.unwrap_or(&String::new()) )); - let bin_path = format!("{}/{hash}", CSHARP_CACHE_DIR); + let bin_path = format!("{}/{hash}", *CSHARP_CACHE_DIR); let remote_path = format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}"); - let (cache, cache_logs) = - crate::global_cache::load_cache(&bin_path, &remote_path, false).await; + let (cache, cache_logs) = crate::global_cache::load_cache(&bin_path, &remote_path, false).await; let cache_logs = if cache { #[cfg(unix)] @@ -591,11 +587,11 @@ pub async fn handle_csharp_job( "run.config.proto", &NSJAIL_CONFIG_RUN_CSHARP_CONTENT .replace("{JOB_DIR}", job_dir) - .replace("{CACHE_DIR}", CSHARP_CACHE_DIR) + .replace("{CACHE_DIR}", &*CSHARP_CACHE_DIR) .replace("{CACHE_HASH}", &hash) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) .replace("{SHARED_MOUNT}", shared_mount) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL), )?; let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str()); @@ -608,8 +604,8 @@ pub async fn handle_csharp_job( .env("PATH", PATH_ENV.as_str()) .env("TZ", TZ_ENV.as_str()) .env("BASE_INTERNAL_URL", base_internal_url) - .env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR) - .env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget")) + .env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR) + .env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR)) .env("DOTNET_CLI_TELEMETRY_OPTOUT", "true") .env("DOTNET_NOLOGO", "true") .env("DOTNET_ROOT", DOTNET_ROOT.as_str()) @@ -640,8 +636,8 @@ pub async fn handle_csharp_job( .envs(get_proxy_envs_for_lang(&ScriptLang::CSharp).await?) .env("PATH", PATH_ENV.as_str()) .env("TZ", TZ_ENV.as_str()) - .env("DOTNET_CLI_HOME", CSHARP_CACHE_DIR) - .env("NUGET_PACKAGES", format!("{CSHARP_CACHE_DIR}/nuget")) + .env("DOTNET_CLI_HOME", &*CSHARP_CACHE_DIR) + .env("NUGET_PACKAGES", format!("{}/nuget", *CSHARP_CACHE_DIR)) .env("DOTNET_CLI_TELEMETRY_OPTOUT", "true") .env("DOTNET_NOLOGO", "true") .env("DOTNET_ROOT", DOTNET_ROOT.as_str()) diff --git a/backend/windmill-worker/src/deno_executor.rs b/backend/windmill-worker/src/deno_executor.rs index 66d8ae8673..19831eadd0 100644 --- a/backend/windmill-worker/src/deno_executor.rs +++ b/backend/windmill-worker/src/deno_executor.rs @@ -443,7 +443,8 @@ try {{ } let allow_read = format!( - "--allow-read=./,/tmp/windmill/cache/deno/,{}", + "--allow-read=./,{}/,{}", + *DENO_CACHE_DIR, DENO_PATH.as_str() ); if let Some(deno_flags) = DENO_FLAGS.as_ref() { @@ -504,7 +505,8 @@ try {{ *has_stream = handle_result.result_stream.is_some(); // logs.push_str(format!("execute: {:?}\n", start.elapsed().as_millis()).as_str()); - if let Err(e) = tokio::fs::remove_dir_all(format!("{DENO_CACHE_DIR}/gen/file/{job_dir}")).await + if let Err(e) = + tokio::fs::remove_dir_all(format!("{}/gen/file/{job_dir}", *DENO_CACHE_DIR)).await { tracing::error!("failed to remove deno gen tmp cache dir: {}", e); } diff --git a/backend/windmill-worker/src/duckdb_executor.rs b/backend/windmill-worker/src/duckdb_executor.rs index 73136e1cc0..45e2f647a5 100644 --- a/backend/windmill-worker/src/duckdb_executor.rs +++ b/backend/windmill-worker/src/duckdb_executor.rs @@ -161,6 +161,22 @@ pub async fn do_duckdb( let base_internal_url = client.base_internal_url.clone(); let w_id = job.workspace_id.clone(); + if annotations.prepare { + let result = tokio::task::spawn_blocking(move || { + prepare_duckdb_ffi_safe( + query_block_list.iter().map(String::as_str), + &token, + &base_internal_url, + &w_id, + ) + }) + .await + .map_err(|e| Error::from(to_anyhow(e))) + .and_then(|r| r)?; + + return Ok(result); + } + let result = tokio::task::spawn_blocking(move || { run_duckdb_ffi_safe( query_block_list.iter().map(String::as_str), @@ -248,6 +264,18 @@ struct DuckDbFfiLib { collect_first_row_only: bool, ) -> *mut c_char, >, + prepare_duckdb_ffi: Option< + Symbol< + 'static, + unsafe extern "C" fn( + query_block_list: *const *const c_char, + query_block_list_count: usize, + token: *const c_char, + base_internal_url: *const c_char, + w_id: *const c_char, + ) -> *mut c_char, + >, + >, free_cstr: Symbol<'static, unsafe extern "C" fn(string: *mut c_char) -> ()>, } @@ -307,8 +335,11 @@ impl DuckDbFfiLib { } } + let prepare_duckdb_ffi = unsafe { lib.get(b"prepare_duckdb_ffi").ok() }; + Ok(DuckDbFfiLib { run_duckdb_ffi: unsafe { lib.get(b"run_duckdb_ffi").map_err(to_anyhow)? }, + prepare_duckdb_ffi, free_cstr: unsafe { lib.get(b"free_cstr").map_err(to_anyhow)? }, }) } @@ -388,6 +419,56 @@ fn run_duckdb_ffi_safe<'a>( } } +fn prepare_duckdb_ffi_safe<'a>( + query_block_list: impl Iterator, + token: &str, + base_internal_url: &str, + w_id: &str, +) -> Result> { + let query_block_list = query_block_list + .map(|s| { + CString::new(s).map_err(|e| { + Error::ExecutionErr(format!("Failed CString conversion: {}", e.to_string())) + }) + }) + .collect::>>()?; + let query_block_list = query_block_list + .iter() + .map(|s| s.as_ptr()) + .collect::>(); + + let token = CString::new(token).map_err(to_anyhow)?; + let base_internal_url = CString::new(base_internal_url).map_err(to_anyhow)?; + let w_id = CString::new(w_id).map_err(to_anyhow)?; + + let lib = DuckDbFfiLib::get_singleton()?; + let prepare_fn = lib.prepare_duckdb_ffi.as_ref().ok_or_else(|| { + Error::InternalErr( + "prepare_duckdb_ffi not available in duckdb ffi library. Please update to the latest windmill_duckdb_ffi_lib.".to_string(), + ) + })?; + let free_cstr = &lib.free_cstr; + + let result_str = unsafe { + let ptr = prepare_fn( + query_block_list.as_ptr(), + query_block_list.len(), + token.as_ptr(), + base_internal_url.as_ptr(), + w_id.as_ptr(), + ); + let str = CStr::from_ptr(ptr).to_string_lossy().to_string(); + free_cstr(ptr); + str + }; + + if result_str.starts_with("ERROR") { + Err(Error::ExecutionErr(result_str[6..].to_string())) + } else { + Ok(serde_json::value::RawValue::from_string(result_str).map_err(to_anyhow)?) + } +} + struct ParsedAttachDbResource<'a> { resource_path: &'a str, name: &'a str, diff --git a/backend/windmill-worker/src/global_cache.rs b/backend/windmill-worker/src/global_cache.rs index 812be2fc7a..ea5a9ded46 100644 --- a/backend/windmill-worker/src/global_cache.rs +++ b/backend/windmill-worker/src/global_cache.rs @@ -18,8 +18,8 @@ pub async fn build_tar_and_push( custom_folder_name: Option, platform_agnostic: bool, ) -> error::Result<()> { - use windmill_object_store::object_store_reexports::Path; use tokio::fs::create_dir_all; + use windmill_object_store::object_store_reexports::Path; use crate::TAR_PYBASE_CACHE_DIR; @@ -33,7 +33,7 @@ pub async fn build_tar_and_push( folder.split("/").last().unwrap().to_owned() }; - let prefix = &format!("{TAR_PYBASE_CACHE_DIR}/{}", lang); + let prefix = &format!("{}/{}", *TAR_PYBASE_CACHE_DIR, lang); let tar_path = format!("{prefix}/{folder_name}_tar.tar"); create_dir_all(prefix).await?; @@ -197,7 +197,9 @@ pub async fn exists_in_cache(bin_path: &str, _remote_path: &str) -> bool { #[cfg(all(feature = "enterprise", feature = "parquet"))] if let Some(os) = windmill_object_store::get_object_store().await { return os - .get(&windmill_object_store::object_store_reexports::Path::from(_remote_path)) + .get(&windmill_object_store::object_store_reexports::Path::from( + _remote_path, + )) .await .is_ok(); } @@ -221,7 +223,7 @@ pub async fn save_cache( let file_to_cache = if is_dir { let tar_path = format!( "{}/tar/{}_tar.tar", - windmill_common::worker::ROOT_CACHE_DIR, + *windmill_common::worker::ROOT_CACHE_DIR, local_cache_path .split("/") .last() diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index 35307baa72..29fff1a2ba 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -2,6 +2,7 @@ use crate::{common::MaybeLock, get_proxy_envs_for_lang}; use std::{collections::HashMap, fs::DirBuilder, process::Stdio}; use windmill_common::scripts::ScriptLang; +use crate::global_cache::save_cache; use itertools::Itertools; use serde_json::value::RawValue; use tokio::{ @@ -15,7 +16,6 @@ use windmill_common::{ utils::calculate_hash, worker::{write_file, Connection, GoAnnotations}, }; -use crate::global_cache::save_cache; use windmill_parser_go::{parse_go_imports, REQUIRE_PARSE}; use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; @@ -108,10 +108,9 @@ pub async fn handle_go_job( .expect("could not create go job dir"); let hash = calculate_hash(&format!("{}{:?}v2", inner_content, &maybe_lock)); - let bin_path = format!("{}/{hash}", GO_BIN_CACHE_DIR); + let bin_path = format!("{}/{hash}", *GO_BIN_CACHE_DIR); let remote_path = format!("{GO_OBJECT_STORE_PREFIX}{hash}"); - let (cache, cache_logs) = - crate::global_cache::load_cache(&bin_path, &remote_path, false).await; + let (cache, cache_logs) = crate::global_cache::load_cache(&bin_path, &remote_path, false).await; let (skip_go_mod, skip_tidy) = if cache { (true, true) @@ -238,15 +237,15 @@ func Run(req Req) (interface{{}}, error){{ .env("GOPATH", { #[cfg(unix)] { - GO_CACHE_DIR + GO_CACHE_DIR.as_str() } #[cfg(windows)] { - windows_gopath() + &windows_gopath() } }) .env("HOME", HOME_ENV.as_str()) - .env("GOCACHE", GO_CACHE_DIR) + .env("GOCACHE", GO_CACHE_DIR.as_str()) .envs(PROXY_ENVS.clone()) .args(vec!["build", "main.go"]) .stdout(Stdio::piped()) @@ -347,7 +346,7 @@ func Run(req Req) (interface{{}}, error){{ .replace("{JOB_DIR}", job_dir) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) .replace("{SHARED_MOUNT}", shared_mount) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL), )?; let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str()); @@ -384,11 +383,11 @@ func Run(req Req) (interface{{}}, error){{ .env("GOPATH", { #[cfg(unix)] { - GO_CACHE_DIR + GO_CACHE_DIR.as_str() } #[cfg(windows)] { - windows_gopath() + &windows_gopath() } }) .env("HOME", HOME_ENV.as_str()); @@ -508,7 +507,7 @@ pub async fn install_go_dependencies( #[cfg(windows)] child_cmd.env("GOPATH", windows_gopath()); #[cfg(unix)] - child_cmd.env("GOPATH", GO_CACHE_DIR); + child_cmd.env("GOPATH", GO_CACHE_DIR.as_str()); #[cfg(windows)] set_windows_env_vars(&mut child_cmd); @@ -591,11 +590,11 @@ pub async fn install_go_dependencies( .env("GOPATH", { #[cfg(unix)] { - GO_CACHE_DIR + GO_CACHE_DIR.as_str() } #[cfg(windows)] { - windows_gopath() + &windows_gopath() } }) .args(vec!["mod", mod_command]) diff --git a/backend/windmill-worker/src/java_executor.rs b/backend/windmill-worker/src/java_executor.rs index 49129848ce..efbe403145 100644 --- a/backend/windmill-worker/src/java_executor.rs +++ b/backend/windmill-worker/src/java_executor.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, path::PathBuf, process::Stdio}; +use crate::global_cache::save_cache; use anyhow::{anyhow, bail}; use async_recursion::async_recursion; use itertools::Itertools; @@ -15,7 +16,6 @@ use windmill_common::{ utils::calculate_hash, worker::{copy_dir_recursively, write_file, Connection}, }; -use crate::global_cache::save_cache; use windmill_parser::Arg; use windmill_parser_java::parse_java_sig_meta; use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; @@ -185,8 +185,8 @@ pub async fn resolve<'a>( cmd.env_clear() .current_dir(job_dir.to_owned()) .env("PATH", PATH_ENV.as_str()) - .env("HOME", JAVA_HOME_DIR) - .env("COURSIER_CACHE", COURSIER_CACHE_DIR) + .env("HOME", &*JAVA_HOME_DIR) + .env("COURSIER_CACHE", &*COURSIER_CACHE_DIR) .envs(PROXY_ENVS.clone()); // Configure proxies @@ -208,7 +208,7 @@ pub async fn resolve<'a>( cmd.arg(&format!("-Dhttp.nonProxyHosts=\"{}\"", val)); } } - cmd.arg(&format!("-Duser.home={}", JAVA_HOME_DIR)); + cmd.arg(&format!("-Duser.home={}", *JAVA_HOME_DIR)); if metadata(TRUST_STORE_PATH.clone()).await.is_ok() { cmd.args(&[ &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), @@ -223,7 +223,7 @@ pub async fn resolve<'a>( "--parallel", &format!("{}", *JAVA_CONCURRENT_DOWNLOADS), "--cache", - COURSIER_CACHE_DIR, + &*COURSIER_CACHE_DIR, ]) .args(&get_repos(job_id, w_id, conn).await) .args(&deps.split("\n").collect_vec()) @@ -276,7 +276,8 @@ async fn install<'a>( match (it.next(), it.next(), it.next()) { (Some(group_id), Some(artifact_id), Some(version)) => { let path = format!( - "{JAVA_REPOSITORY_DIR}/{}/{artifact_id}/{version}", + "{}/{}/{artifact_id}/{version}", + *JAVA_REPOSITORY_DIR, group_id.replace(".", "/") ); Ok(RequiredDependency { @@ -312,7 +313,7 @@ async fn install<'a>( metadata(TRUST_STORE_PATH.clone()).await, ); let job_dir = job_dir.to_owned(); - let fetch_dir = format!("{JAVA_CACHE_DIR}/tmp-fetch-{}", Uuid::new_v4()); + let fetch_dir = format!("{}/tmp-fetch-{}", *JAVA_CACHE_DIR, Uuid::new_v4()); let fetch_dir2 = fetch_dir.clone(); par_install_language_dependencies_all_at_once( deps, @@ -334,8 +335,8 @@ async fn install<'a>( cmd.env_clear() .current_dir(&job_dir) .env("PATH", PATH_ENV.as_str()) - .env("HOME", JAVA_HOME_DIR) - .env("COURSIER_CACHE", COURSIER_CACHE_DIR) + .env("HOME", &*JAVA_HOME_DIR) + .env("COURSIER_CACHE", &*COURSIER_CACHE_DIR) .envs(PROXY_ENVS.clone()); // Configure proxies { @@ -357,7 +358,7 @@ async fn install<'a>( } } - cmd.arg(&format!("-Duser.home={}", JAVA_HOME_DIR)); + cmd.arg(&format!("-Duser.home={}", *JAVA_HOME_DIR)); if trust_store_metadata.is_ok() { cmd.args(&[ &format!("-Djavax.net.ssl.trustStore={}", *TRUST_STORE_PATH), @@ -400,7 +401,7 @@ async fn install<'a>( if depth == 3 { copy_dir_recursively( &PathBuf::from(path), - &PathBuf::from(JAVA_REPOSITORY_DIR), + &PathBuf::from(&*JAVA_REPOSITORY_DIR), )?; return Ok(()); @@ -465,7 +466,7 @@ async fn compile<'a>( let reserved_variables = get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?; let hash = compute_hash(inner_content, *requirements_o); - let bin_path = format!("{}/{hash}", JAVA_CACHE_DIR); + let bin_path = format!("{}/{hash}", *JAVA_CACHE_DIR); let remote_path = format!("java_jar/{hash}"); let (cache, ..) = crate::global_cache::load_cache(&bin_path, &remote_path, true).await; @@ -501,7 +502,7 @@ async fn compile<'a>( cmd.env_clear() .current_dir(job_dir.to_owned()) .env("PATH", PATH_ENV.as_str()) - .env("HOME", JAVA_HOME_DIR) + .env("HOME", &*JAVA_HOME_DIR) .env("BASE_INTERNAL_URL", base_internal_url) .envs(envs) .envs(reserved_variables) @@ -604,7 +605,7 @@ async fn run<'a>( "run.config.proto", &NSJAIL_CONFIG_RUN_JAVA_CONTENT .replace("{JOB_DIR}", job_dir) - .replace("{CACHE_DIR}", JAVA_CACHE_DIR) + .replace("{CACHE_DIR}", &*JAVA_CACHE_DIR) .replace("{SHARED_MOUNT}", &shared_mount) // .replace("{CACHED_TARGET}", &shared_mount) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()), @@ -613,7 +614,7 @@ async fn run<'a>( cmd.env_clear() .current_dir(job_dir) .env("PATH", PATH_ENV.as_str()) - .env("HOME", JAVA_HOME_DIR) + .env("HOME", &*JAVA_HOME_DIR) .env("BASE_INTERNAL_URL", base_internal_url) .envs(envs) .envs(reserved_variables) @@ -671,7 +672,7 @@ async fn run<'a>( cmd.env_clear() .current_dir(job_dir.to_owned()) .env("PATH", PATH_ENV.as_str()) - .env("HOME", JAVA_HOME_DIR) + .env("HOME", &*JAVA_HOME_DIR) .env("BASE_INTERNAL_URL", base_internal_url) .envs(envs) .envs(reserved_variables); diff --git a/backend/windmill-worker/src/nu_executor.rs b/backend/windmill-worker/src/nu_executor.rs index a4320a3612..ca309105b5 100644 --- a/backend/windmill-worker/src/nu_executor.rs +++ b/backend/windmill-worker/src/nu_executor.rs @@ -16,8 +16,8 @@ use crate::{ build_command_with_isolation, create_args_and_out_file, get_reserved_variables, read_result, start_child_process, OccupancyMetrics, DEV_CONF_NSJAIL, }, - get_proxy_envs_for_lang, handle_child, is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV, - TRACING_PROXY_CA_CERT_PATH, + get_proxy_envs_for_lang, handle_child, is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH, + PATH_ENV, TRACING_PROXY_CA_CERT_PATH, }; use windmill_common::client::AuthedClient; use windmill_common::scripts::ScriptLang; @@ -253,7 +253,7 @@ async fn run<'a>( .replace("{NU_PATH}", &NU_PATH) .replace("{SHARED_MOUNT}", &shared_mount) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL), )?; let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str()); diff --git a/backend/windmill-worker/src/pwsh_executor.rs b/backend/windmill-worker/src/pwsh_executor.rs index acae81a588..961a49a58a 100644 --- a/backend/windmill-worker/src/pwsh_executor.rs +++ b/backend/windmill-worker/src/pwsh_executor.rs @@ -159,7 +159,7 @@ try { async fn scan_module_directories() -> Result, Error> { let mut module_dirs = HashMap::new(); - let cache_dir = std::path::Path::new(POWERSHELL_CACHE_DIR); + let cache_dir = std::path::Path::new(&*POWERSHELL_CACHE_DIR); if let Ok(entries) = fs::read_dir(cache_dir) { for entry in entries { @@ -391,7 +391,7 @@ pub async fn handle_powershell_job( .join(", "); let install_string = generate_powershell_install_code() - .replace("{path}", POWERSHELL_CACHE_DIR) + .replace("{path}", &*POWERSHELL_CACHE_DIR) .replace("{job_id}", &job.id.to_string()) .replace("{has_private_repo}", &format!("${has_private_repo}")) .replace("{has_credentials}", &format!("${has_credentials}")) @@ -442,7 +442,7 @@ $PSModulePathBackup = $env:PSModulePath $env:PSModulePath = \"$PSHome/Modules\" Get-Module -ListAvailable | Import-Module $env:PSModulePath = \"{}:$PSModulePathBackup\"", - POWERSHELL_CACHE_DIR + *POWERSHELL_CACHE_DIR ); #[cfg(windows)] @@ -452,7 +452,7 @@ $PSModulePathBackup = $env:PSModulePath $env:PSModulePath = \"C:\\Program Files\\PowerShell\\7\\Modules\" Get-Module -ListAvailable | Import-Module $env:PSModulePath = \"{};$PSModulePathBackup\"", - POWERSHELL_CACHE_DIR + *POWERSHELL_CACHE_DIR ); // NOTE: powershell error handling / termination is quite tricky compared to bash @@ -525,7 +525,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", .replace("{JOB_DIR}", job_dir) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) .replace("{SHARED_MOUNT}", shared_mount) - .replace("{CACHE_DIR}", POWERSHELL_CACHE_DIR), + .replace("{CACHE_DIR}", &*POWERSHELL_CACHE_DIR), )?; let cmd_args = vec![ "--config", diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index 047521a0b4..b289f05380 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -278,7 +278,7 @@ pub async fn uv_pip_compile( "requirements.txt", // Target to /tmp/windmill/cache/uv "--cache-dir", - UV_CACHE_DIR, + &*UV_CACHE_DIR, ]; args.extend(["-p", &py_version_str, "--python-preference", "only-managed"]); @@ -805,7 +805,7 @@ mount {{ "run.config.proto", &NSJAIL_CONFIG_RUN_PYTHON3_CONTENT .replace("{JOB_DIR}", job_dir) - .replace("{PY_INSTALL_DIR}", PY_INSTALL_DIR) + .replace("{PY_INSTALL_DIR}", &*PY_INSTALL_DIR) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) .replace("{SHARED_MOUNT}", shared_mount) .replace("{SHARED_DEPENDENCIES}", shared_deps.as_str()) @@ -815,7 +815,7 @@ mount {{ "{ADDITIONAL_PYTHON_PATHS}", additional_python_paths_folders.as_str(), ) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL), )?; } else { @@ -1410,10 +1410,10 @@ async fn spawn_uv_install( &nsjail_proto, NSJAIL_CONFIG_DOWNLOAD_PY_CONTENT .replace("{WORKER_DIR}", worker_dir) - .replace("{PY_INSTALL_DIR}", &PY_INSTALL_DIR) + .replace("{PY_INSTALL_DIR}", &*PY_INSTALL_DIR) .replace("{TARGET_DIR}", &venv_p) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL) .as_str(), )?; diff --git a/backend/windmill-worker/src/python_versions.rs b/backend/windmill-worker/src/python_versions.rs index 52636808a6..78b03deb07 100644 --- a/backend/windmill-worker/src/python_versions.rs +++ b/backend/windmill-worker/src/python_versions.rs @@ -18,6 +18,8 @@ use windmill_common::{ use anyhow::{anyhow, bail}; use windmill_queue::append_logs; +#[cfg(unix)] +use crate::python_executor::UV_PATH; use crate::{ common::{start_child_process, OccupancyMetrics}, handle_child::handle_child, @@ -25,8 +27,6 @@ use crate::{ HOME_ENV, INSTANCE_PYTHON_VERSION, PATH_ENV, PROXY_ENVS, PY_INSTALL_DIR, UV_CACHE_DIR, WIN_ENVS, }; -#[cfg(unix)] -use crate::python_executor::UV_PATH; impl From for PyVAlias { fn from(value: PyV) -> Self { @@ -234,7 +234,8 @@ impl PyV { pub(crate) fn to_cache_dir(&self, ignore_patch: bool) -> String { use windmill_common::worker::ROOT_CACHE_DIR; format!( - "{ROOT_CACHE_DIR}{}", + "{}{}", + *ROOT_CACHE_DIR, self.to_cache_dir_top_level(ignore_patch) ) } @@ -311,7 +312,7 @@ impl PyV { Command::new(uv_cmd) .env_clear() .envs(WIN_ENVS.to_vec()) - .env("UV_CACHE_DIR", UV_CACHE_DIR) + .env("UV_CACHE_DIR", &*UV_CACHE_DIR) .args([ "python", "list", @@ -539,8 +540,8 @@ impl PyV { ]) // TODO: Do we need these? .envs([ - ("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR), - ("UV_CACHE_DIR", UV_CACHE_DIR), + ("UV_PYTHON_INSTALL_DIR", &*PY_INSTALL_DIR), + ("UV_CACHE_DIR", &*UV_CACHE_DIR), ]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -630,11 +631,9 @@ impl PyV { "--system", "--python-preference=only-managed", ]) - .envs([ - ("UV_PYTHON_INSTALL_DIR", PY_INSTALL_DIR), - ("UV_PYTHON_PREFERENCE", "only-managed"), - ("UV_CACHE_DIR", UV_CACHE_DIR), - ]) + .env("UV_PYTHON_INSTALL_DIR", &*PY_INSTALL_DIR) + .env("UV_PYTHON_PREFERENCE", "only-managed") + .env("UV_CACHE_DIR", &*UV_CACHE_DIR) // .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() diff --git a/backend/windmill-worker/src/ruby_executor.rs b/backend/windmill-worker/src/ruby_executor.rs index 869b58d7de..11031fa481 100644 --- a/backend/windmill-worker/src/ruby_executor.rs +++ b/backend/windmill-worker/src/ruby_executor.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, process::Stdio}; use anyhow::anyhow; -use const_format::concatcp; use itertools::Itertools; use regex::Regex; use tokio::{ @@ -122,7 +121,7 @@ pub async fn prepare<'a>( .write_all(&wrap(inner_content)?.into_bytes()) .await?; - let mini_wm_path = format!("{RUBY_CACHE_DIR}/gems/windmill-internal/windmill"); + let mini_wm_path = format!("{}/gems/windmill-internal/windmill", *RUBY_CACHE_DIR); if !std::fs::metadata(&mini_wm_path).is_ok() { fs::create_dir_all(&mini_wm_path).await?; @@ -339,7 +338,7 @@ Your Gemfile syntax will continue to work as-is." &NSJAIL_CONFIG_LOCK_RUBY_CONTENT .replace("{JOB_DIR}", job_dir) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL), // .replace("{BUILD}", &build_dir), )?; let mut cmd = Command::new(NSJAIL_PATH.as_str()); @@ -588,7 +587,7 @@ async fn install<'a>( // 123...zx-activesupport-8.0.2 // ^^^^^^^^ hash based on source and type (GEM or GIT) let handle = format!("{}-{}-{}", hash, pkg, version); - let path = format!("{RUBY_CACHE_DIR}/gems/{}", &handle); + let path = format!("{}/gems/{}", *RUBY_CACHE_DIR, &handle); deps.push(RequiredDependency { path, @@ -632,7 +631,7 @@ async fn install<'a>( &NSJAIL_CONFIG_DOWNLOAD_RUBY_CONTENT .replace("{TARGET}", &dependency.path) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL), // .replace("{BUILD}", &build_dir), )?; let mut cmd = Command::new(NSJAIL_PATH.as_str()); @@ -741,9 +740,9 @@ async fn install<'a>( }; // Include builtin windmill client { - const WM_INTERNAL: &str = concatcp!(RUBY_CACHE_DIR, "/gems/windmill-internal"); - res.top_level_paths.push(WM_INTERNAL.to_owned()); - res.rubylib += format!(":{WM_INTERNAL}").as_str(); + let wm_internal = format!("{}/gems/windmill-internal", *RUBY_CACHE_DIR); + res.top_level_paths.push(wm_internal.clone()); + res.rubylib += format!(":{wm_internal}").as_str(); } Ok(res) } @@ -800,7 +799,7 @@ mount {{ .replace("{JOB_DIR}", job_dir) .replace("{SHARED_MOUNT}", &shared_mount) .replace("{SHARED_DEPENDENCIES}", &shared_deps) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()), )?; diff --git a/backend/windmill-worker/src/rust_executor.rs b/backend/windmill-worker/src/rust_executor.rs index 54d90550b0..216c59f2cd 100644 --- a/backend/windmill-worker/src/rust_executor.rs +++ b/backend/windmill-worker/src/rust_executor.rs @@ -5,6 +5,7 @@ use std::{collections::HashMap, process::Stdio}; use uuid::Uuid; use windmill_parser_rust::parse_rust_deps_into_manifest; +use crate::global_cache::save_cache; use itertools::Itertools; use tokio::{ fs::{create_dir_all, File}, @@ -16,7 +17,6 @@ use windmill_common::{ utils::calculate_hash, worker::{write_file, Connection}, }; -use crate::global_cache::save_cache; use windmill_queue::MiniPulledJob; use windmill_queue::{append_logs, CanceledBy}; @@ -337,7 +337,7 @@ async fn get_build_dir( if !is_sandboxing_enabled() { // If nsjail is disabled then entire worker has shared build directory // It drastically improves cache hit-rate. - Some((format!("{RUST_CACHE_DIR}/build/{worker_name}"), true)) + Some((format!("{}/build/{worker_name}", *RUST_CACHE_DIR), true)) } else { // If nsjail is enabled, having global shared directory is vulnerability and target for an attack // Instead we either: @@ -345,7 +345,8 @@ async fn get_build_dir( // 2. If user is not known or something else goes wrong - use random build dir. This is equivalent to no cache at all. Some(( format!( - "{RUST_CACHE_DIR}/build/{}@{}@{}", + "{}/build/{}@{}@{}", + *RUST_CACHE_DIR, &job.workspace_id, p.replace('/', "."), &job.created_by @@ -355,7 +356,10 @@ async fn get_build_dir( } } }) - .unwrap_or((format!("{RUST_CACHE_DIR}/build/{}", Uuid::new_v4()), false)); + .unwrap_or(( + format!("{}/build/{}", *RUST_CACHE_DIR, Uuid::new_v4()), + false, + )); { let (t, r, g) = ( @@ -449,7 +453,7 @@ pub async fn build_rust_crate( is_preview: bool, ) -> error::Result { ensure_rust_runtime_dirs(); - let bin_path = format!("{}/{hash}", RUST_CACHE_DIR); + let bin_path = format!("{}/{hash}", *RUST_CACHE_DIR); let build_dir = get_build_dir(job, job_dir, conn, worker_name, is_preview).await?; @@ -459,9 +463,9 @@ pub async fn build_rust_crate( "download.config.proto", &NSJAIL_CONFIG_COMPILE_RUST_CONTENT .replace("{JOB_DIR}", job_dir) - .replace("{CACHE_DIR}", RUST_CACHE_DIR) + .replace("{CACHE_DIR}", &*RUST_CACHE_DIR) .replace("{CARGO_HOME}", CARGO_HOME.as_str()) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL) .replace("{BUILD}", &build_dir), )?; @@ -605,14 +609,13 @@ pub async fn handle_rust_job( check_executor_binary_exists("cargo", CARGO_PATH.as_str(), "rust")?; let hash = compute_rust_hash(inner_content, requirements_o); - let bin_path = format!("{}/{hash}", RUST_CACHE_DIR); + let bin_path = format!("{}/{hash}", *RUST_CACHE_DIR); let remote_path = format!("{RUST_OBJECT_STORE_PREFIX}{hash}"); let reserved_variables = get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?; - let (cache, cache_logs) = - crate::global_cache::load_cache(&bin_path, &remote_path, false).await; + let (cache, cache_logs) = crate::global_cache::load_cache(&bin_path, &remote_path, false).await; let cache_logs = if cache { let target = format!("{job_dir}/main"); @@ -669,10 +672,10 @@ pub async fn handle_rust_job( "run.config.proto", &NSJAIL_CONFIG_RUN_RUST_CONTENT .replace("{JOB_DIR}", job_dir) - .replace("{CACHE_DIR}", RUST_CACHE_DIR) + .replace("{CACHE_DIR}", &*RUST_CACHE_DIR) .replace("{CACHE_HASH}", &hash) .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) - .replace("{TRACING_PROXY_CA_CERT_PATH}", TRACING_PROXY_CA_CERT_PATH) + .replace("{TRACING_PROXY_CA_CERT_PATH}", &*TRACING_PROXY_CA_CERT_PATH) .replace("#{DEV}", DEV_CONF_NSJAIL) .replace("{SHARED_MOUNT}", shared_mount), )?; diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index c354d34ee2..49a1048afd 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -37,7 +37,7 @@ use windmill_common::{ utils::{create_directory_async, WarnAfterExt}, worker::{ make_pull_query, write_file, Connection, HttpClient, MAX_TIMEOUT, - MIN_PERIODIC_SCRIPT_INTERVAL_SECONDS, ROOT_CACHE_DIR, ROOT_CACHE_NOMOUNT_DIR, TMP_DIR, + MIN_PERIODIC_SCRIPT_INTERVAL_SECONDS, ROOT_CACHE_DIR, ROOT_CACHE_NOMOUNT_DIR, WINDMILL_DIR, }, worker_group_job_stats::JobStatsMap, KillpillSender, @@ -47,7 +47,6 @@ use windmill_common::{ use windmill_common::ee_oss::LICENSE_KEY_VALID; use anyhow::Result; -use const_format::concatcp; #[cfg(feature = "prometheus")] use prometheus::IntCounter; @@ -196,45 +195,47 @@ use windmill_common::bench::{benchmark_init, benchmark_verify, BenchmarkInfo, Be use windmill_common::add_time; -pub const PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_10"); -pub const PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_11"); -pub const PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_12"); -pub const PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_13"); +lazy_static::lazy_static! { + pub static ref PY310_CACHE_DIR: String = format!("{}python_3_10", *ROOT_CACHE_DIR); + pub static ref PY311_CACHE_DIR: String = format!("{}python_3_11", *ROOT_CACHE_DIR); + pub static ref PY312_CACHE_DIR: String = format!("{}python_3_12", *ROOT_CACHE_DIR); + pub static ref PY313_CACHE_DIR: String = format!("{}python_3_13", *ROOT_CACHE_DIR); -pub const TAR_JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/java"); + pub static ref TAR_JAVA_CACHE_DIR: String = format!("{}tar/java", *ROOT_CACHE_DIR); -pub const UV_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "uv"); -pub const PY_INSTALL_DIR: &str = concatcp!(ROOT_CACHE_DIR, "py_runtime"); -pub const TAR_PYBASE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar"); -pub const DENO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "deno"); -pub const DENO_CACHE_DIR_DEPS: &str = concatcp!(ROOT_CACHE_DIR, "deno/deps"); -pub const DENO_CACHE_DIR_NPM: &str = concatcp!(ROOT_CACHE_DIR, "deno/npm"); + pub static ref UV_CACHE_DIR: String = format!("{}uv", *ROOT_CACHE_DIR); + pub static ref PY_INSTALL_DIR: String = format!("{}py_runtime", *ROOT_CACHE_DIR); + pub static ref TAR_PYBASE_CACHE_DIR: String = format!("{}tar", *ROOT_CACHE_DIR); + pub static ref DENO_CACHE_DIR: String = format!("{}deno", *ROOT_CACHE_DIR); + pub static ref DENO_CACHE_DIR_DEPS: String = format!("{}deno/deps", *ROOT_CACHE_DIR); + pub static ref DENO_CACHE_DIR_NPM: String = format!("{}deno/npm", *ROOT_CACHE_DIR); -pub const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go"); -pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust"); -pub const NU_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "nu"); -pub const CSHARP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "csharp"); + pub static ref GO_CACHE_DIR: String = format!("{}go", *ROOT_CACHE_DIR); + pub static ref RUST_CACHE_DIR: String = format!("{}rust", *ROOT_CACHE_DIR); + pub static ref NU_CACHE_DIR: String = format!("{}nu", *ROOT_CACHE_DIR); + pub static ref CSHARP_CACHE_DIR: String = format!("{}csharp", *ROOT_CACHE_DIR); -// Java -pub const JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "java"); -pub const COURSIER_CACHE_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/coursier-cache"); -pub const JAVA_REPOSITORY_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/repository"); -pub const JAVA_HOME_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/home"); + // Java + pub static ref JAVA_CACHE_DIR: String = format!("{}java", *ROOT_CACHE_DIR); + pub static ref COURSIER_CACHE_DIR: String = format!("{}/coursier-cache", *JAVA_CACHE_DIR); + pub static ref JAVA_REPOSITORY_DIR: String = format!("{}/repository", *JAVA_CACHE_DIR); + pub static ref JAVA_HOME_DIR: String = format!("{}/home", *JAVA_CACHE_DIR); -// Ruby -pub const RUBY_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "ruby"); + // Ruby + pub static ref RUBY_CACHE_DIR: String = format!("{}ruby", *ROOT_CACHE_DIR); -// for related places search: ADD_NEW_LANG -pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "bun"); -pub const BUN_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun"); -pub const BUN_CODEBASE_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "script_bundle"); + // for related places search: ADD_NEW_LANG + pub static ref BUN_CACHE_DIR: String = format!("{}bun", *ROOT_CACHE_NOMOUNT_DIR); + pub static ref BUN_BUNDLE_CACHE_DIR: String = format!("{}bun", *ROOT_CACHE_DIR); + pub static ref BUN_CODEBASE_BUNDLE_CACHE_DIR: String = format!("{}script_bundle", *ROOT_CACHE_NOMOUNT_DIR); -pub const GO_BIN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "gobin"); -pub const POWERSHELL_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "powershell"); -pub const COMPOSER_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "composer"); + pub static ref GO_BIN_CACHE_DIR: String = format!("{}gobin", *ROOT_CACHE_DIR); + pub static ref POWERSHELL_CACHE_DIR: String = format!("{}powershell", *ROOT_CACHE_DIR); + pub static ref COMPOSER_CACHE_DIR: String = format!("{}composer", *ROOT_CACHE_DIR); -pub const TRACING_PROXY_CA_CERT_PATH: &str = - concatcp!(ROOT_CACHE_NOMOUNT_DIR, "tracing_proxy_ca.pem"); + pub static ref TRACING_PROXY_CA_CERT_PATH: String = + format!("{}tracing_proxy_ca.pem", *ROOT_CACHE_NOMOUNT_DIR); +} const NUM_SECS_PING: u64 = 5; const NUM_SECS_READINGS: u64 = 60; @@ -1375,7 +1376,7 @@ pub async fn run_worker( let start_time = Instant::now(); - let worker_dir = format!("{TMP_DIR}/{worker_name}"); + let worker_dir = format!("{}/{worker_name}", *WINDMILL_DIR); tracing::debug!(worker = %worker_name, hostname = %hostname, worker_dir = %worker_dir, "Creating worker dir"); #[cfg(feature = "python")] diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 41204c956a..cc1417c4b3 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.648.0"; +export const VERSION = "v1.649.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 38b68bb0f4..b584c540d8 100755 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -67,7 +67,7 @@ export { workspaceAdd, }; -export const VERSION = "1.648.0"; +export const VERSION = "1.649.0"; // Re-exported from constants.ts to maintain backwards compatibility export { WM_FORK_PREFIX } from "./core/constants.ts"; diff --git a/console-bk-leftup.txt b/console-bk-leftup.txt new file mode 100644 index 0000000000..b526ad9ffc --- /dev/null +++ b/console-bk-leftup.txt @@ -0,0 +1,694 @@ +Total messages: 23 (Errors: 0, Warnings: 1) + +[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65 +[DEBUG] layoutNodes same nodes @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:516 +[DEBUG] [vite] connecting... @ http://localhost:5183/@vite/client:730 +[DEBUG] [vite] connected. @ http://localhost:5183/@vite/client:832 +[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65 +[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65 +[DEBUG] layoutNodes 3 @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:521 +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:535 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-Input-Result", + "sources": [ + "Input" + ], + "targets": [ + "Result" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:536 +[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024 +[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772544066222:100 +[LOG] ELK result: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34, + "$H": 277, + "x": 12, + "y": 12 + }, + { + "id": "Input", + "width": 275, + "height": 34, + "$H": 279, + "x": 12, + "y": 108 + }, + { + "id": "Result", + "width": 275, + "height": 34, + "$H": 281, + "x": 12, + "y": 204 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ], + "sections": [ + { + "id": "e-Trigger-Input_s0", + "startPoint": { + "x": 150, + "y": 46 + }, + "endPoint": { + "x": 150, + "y": 108 + }, + "incomingShape": "Trigger", + "outgoingShape": "Input" + } + ], + "container": "root" + }, + { + "id": "e-Input-Result", + "sources": [ + "Input" + ], + "targets": [ + "Result" + ], + "sections": [ + { + "id": "e-Input-Result_s0", + "startPoint": { + "x": 150, + "y": 142 + }, + "endPoint": { + "x": 150, + "y": 204 + }, + "incomingShape": "Input", + "outgoingShape": "Result" + } + ], + "container": "root" + } + ], + "$H": 13, + "x": 0, + "y": 0, + "width": 299, + "height": 250 +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:540 +[LOG] Welcome back admin to testing @ http://localhost:5183/src/routes/(root)/+layout.svelte?t=1772535251800:68 +[WARNING] using deprecated parameters for the initialization function; pass a single object instead @ http://localhost:5183/node_modules/@sveltejs/kit/src/runtime/client/client.js?v=5452fe86:3211 +[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65 +[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65 +[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65 +[DEBUG] layoutNodes 14 @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:521 +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:535 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + }, + { + "id": "a-end", + "width": 275, + "height": 34 + }, + { + "id": "a", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.spacing.nodeNode": "50", + "elk.separateConnectedComponents": "false", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES", + "elk.layered.nodePlacement.strategy": "BRANDES_KOEPF", + "elk.layered.nodePlacement.bk.fixedAlignment": "LEFTUP" + }, + "children": [ + { + "id": "a-branch-0", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "b", + "width": 275, + "height": 34 + }, + { + "id": "c", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-b-c", + "sources": [ + "b" + ], + "targets": [ + "c" + ] + } + ] + }, + { + "id": "a-branch-1", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "d", + "width": 275, + "height": 34 + } + ], + "edges": [] + }, + { + "id": "a-branch-2", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "e", + "width": 275, + "height": 34 + }, + { + "id": "f", + "width": 275, + "height": 34 + }, + { + "id": "g", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-e-f", + "sources": [ + "e" + ], + "targets": [ + "f" + ] + }, + { + "id": "e-f-g", + "sources": [ + "f" + ], + "targets": [ + "g" + ] + } + ] + } + ] + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-a-end-Result", + "sources": [ + "a-end" + ], + "targets": [ + "Result" + ] + }, + { + "id": "e-Input-a", + "sources": [ + "Input" + ], + "targets": [ + "a" + ] + }, + { + "id": "e-a-a-end", + "sources": [ + "a" + ], + "targets": [ + "a-end" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:536 +[LOG] ELK result: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34, + "$H": 352, + "x": 385, + "y": 12 + }, + { + "id": "Input", + "width": 275, + "height": 34, + "$H": 354, + "x": 385, + "y": 108 + }, + { + "id": "Result", + "width": 275, + "height": 34, + "$H": 356, + "x": 385, + "y": 636 + }, + { + "id": "a-end", + "width": 275, + "height": 34, + "$H": 358, + "x": 385, + "y": 540 + }, + { + "id": "a", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.spacing.nodeNode": "50", + "elk.separateConnectedComponents": "false", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES", + "elk.layered.nodePlacement.strategy": "BRANDES_KOEPF", + "elk.layered.nodePlacement.bk.fixedAlignment": "LEFTUP" + }, + "children": [ + { + "id": "a-branch-0", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "b", + "width": 275, + "height": 34, + "$H": 364, + "x": 12, + "y": 12 + }, + { + "id": "c", + "width": 275, + "height": 34, + "$H": 366, + "x": 12, + "y": 108 + } + ], + "edges": [ + { + "id": "e-b-c", + "sources": [ + "b" + ], + "targets": [ + "c" + ], + "sections": [ + { + "id": "e-b-c_s0", + "startPoint": { + "x": 149.5, + "y": 46 + }, + "endPoint": { + "x": 149.5, + "y": 108 + }, + "incomingShape": "b", + "outgoingShape": "c" + } + ], + "container": "a-branch-0" + } + ], + "$H": 362, + "x": 12, + "y": 60, + "width": 299, + "height": 154 + }, + { + "id": "a-branch-1", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "d", + "width": 275, + "height": 34, + "$H": 370, + "x": 12, + "y": 12 + } + ], + "edges": [], + "$H": 368, + "x": 361, + "y": 108, + "width": 299, + "height": 58 + }, + { + "id": "a-branch-2", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "e", + "width": 275, + "height": 34, + "$H": 374, + "x": 12, + "y": 12 + }, + { + "id": "f", + "width": 275, + "height": 34, + "$H": 376, + "x": 12, + "y": 108 + }, + { + "id": "g", + "width": 275, + "height": 34, + "$H": 378, + "x": 12, + "y": 204 + } + ], + "edges": [ + { + "id": "e-e-f", + "sources": [ + "e" + ], + "targets": [ + "f" + ], + "sections": [ + { + "id": "e-e-f_s0", + "startPoint": { + "x": 149.5, + "y": 46 + }, + "endPoint": { + "x": 149.5, + "y": 108 + }, + "incomingShape": "e", + "outgoingShape": "f" + } + ], + "container": "a-branch-2" + }, + { + "id": "e-f-g", + "sources": [ + "f" + ], + "targets": [ + "g" + ], + "sections": [ + { + "id": "e-f-g_s0", + "startPoint": { + "x": 149.5, + "y": 142 + }, + "endPoint": { + "x": 149.5, + "y": 204 + }, + "incomingShape": "f", + "outgoingShape": "g" + } + ], + "container": "a-branch-2" + } + ], + "$H": 372, + "x": 710, + "y": 12, + "width": 299, + "height": 250 + } + ], + "$H": 360, + "x": 12, + "y": 204, + "width": 1021, + "height": 274 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ], + "sections": [ + { + "id": "e-Trigger-Input_s0", + "startPoint": { + "x": 523, + "y": 46 + }, + "endPoint": { + "x": 523, + "y": 108 + }, + "incomingShape": "Trigger", + "outgoingShape": "Input" + } + ], + "container": "root" + }, + { + "id": "e-a-end-Result", + "sources": [ + "a-end" + ], + "targets": [ + "Result" + ], + "sections": [ + { + "id": "e-a-end-Result_s0", + "startPoint": { + "x": 523, + "y": 574 + }, + "endPoint": { + "x": 523, + "y": 636 + }, + "incomingShape": "a-end", + "outgoingShape": "Result" + } + ], + "container": "root" + }, + { + "id": "e-Input-a", + "sources": [ + "Input" + ], + "targets": [ + "a" + ], + "sections": [ + { + "id": "e-Input-a_s0", + "startPoint": { + "x": 523, + "y": 142 + }, + "endPoint": { + "x": 523, + "y": 204 + }, + "incomingShape": "Input", + "outgoingShape": "a" + } + ], + "container": "root" + }, + { + "id": "e-a-a-end", + "sources": [ + "a" + ], + "targets": [ + "a-end" + ], + "sections": [ + { + "id": "e-a-a-end_s0", + "startPoint": { + "x": 523, + "y": 478 + }, + "endPoint": { + "x": 523, + "y": 540 + }, + "incomingShape": "a", + "outgoingShape": "a-end" + } + ], + "container": "root" + } + ], + "$H": 350, + "x": 0, + "y": 0, + "width": 1045, + "height": 682 +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:540 +[DEBUG] Building graph @ http://localhost:5183/src/lib/components/graph/graphBuilder.svelte.ts?t=1772535251657:65 +[DEBUG] layoutNodes same nodes @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772544066222:516 \ No newline at end of file diff --git a/console-branchall.txt b/console-branchall.txt new file mode 100644 index 0000000000..a265fb88e8 --- /dev/null +++ b/console-branchall.txt @@ -0,0 +1,425 @@ +Total messages: 36 (Errors: 0, Warnings: 1) +Returning 22 messages for level "info" + +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-Input-Result", + "sources": [ + "Input" + ], + "targets": [ + "Result" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532 +[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024 +[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772543608757:100 +[LOG] Welcome back admin to testing @ http://localhost:5183/src/routes/(root)/+layout.svelte?t=1772535251800:68 +[WARNING] using deprecated parameters for the initialization function; pass a single object instead @ http://localhost:5183/node_modules/@sveltejs/kit/src/runtime/client/client.js?v=5452fe86:3211 +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + }, + { + "id": "a-end", + "width": 275, + "height": 34 + }, + { + "id": "a", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "RIGHT", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "a-branch-0", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "b", + "width": 275, + "height": 34 + }, + { + "id": "c", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-b-c", + "sources": [ + "b" + ], + "targets": [ + "c" + ] + } + ] + }, + { + "id": "a-branch-1", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "d", + "width": 275, + "height": 34 + } + ], + "edges": [] + }, + { + "id": "a-branch-2", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "e", + "width": 275, + "height": 34 + }, + { + "id": "f", + "width": 275, + "height": 34 + }, + { + "id": "g", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-e-f", + "sources": [ + "e" + ], + "targets": [ + "f" + ] + }, + { + "id": "e-f-g", + "sources": [ + "f" + ], + "targets": [ + "g" + ] + } + ] + } + ] + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-a-end-Result", + "sources": [ + "a-end" + ], + "targets": [ + "Result" + ] + }, + { + "id": "e-Input-a", + "sources": [ + "Input" + ], + "targets": [ + "a" + ] + }, + { + "id": "e-a-a-end", + "sources": [ + "a" + ], + "targets": [ + "a-end" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532 +[LOG] RawScript module b without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149 +[LOG] Parsing assets for RawScript module b @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121 +[LOG] RawScript module c without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149 +[LOG] Parsing assets for RawScript module c @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121 +[LOG] RawScript module d without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149 +[LOG] Parsing assets for RawScript module d @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121 +[LOG] RawScript module e without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149 +[LOG] Parsing assets for RawScript module e @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121 +[LOG] RawScript module f without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149 +[LOG] Parsing assets for RawScript module f @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121 +[LOG] RawScript module g without assets field, parsing @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:149 +[LOG] Parsing assets for RawScript module g @ http://localhost:5183/src/lib/components/flows/FlowAssetsHandler.svelte?t=1772535251800:121 +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + }, + { + "id": "a-end", + "width": 275, + "height": 34 + }, + { + "id": "a", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "RIGHT", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "a-branch-0", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "b", + "width": 275, + "height": 34 + }, + { + "id": "c", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-b-c", + "sources": [ + "b" + ], + "targets": [ + "c" + ] + } + ] + }, + { + "id": "a-branch-1", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "d", + "width": 275, + "height": 34 + } + ], + "edges": [] + }, + { + "id": "a-branch-2", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "e", + "width": 275, + "height": 34 + }, + { + "id": "f", + "width": 275, + "height": 34 + }, + { + "id": "g", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-e-f", + "sources": [ + "e" + ], + "targets": [ + "f" + ] + }, + { + "id": "e-f-g", + "sources": [ + "f" + ], + "targets": [ + "g" + ] + } + ] + } + ] + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-a-end-Result", + "sources": [ + "a-end" + ], + "targets": [ + "Result" + ] + }, + { + "id": "e-Input-a", + "sources": [ + "Input" + ], + "targets": [ + "a" + ] + }, + { + "id": "e-a-a-end", + "sources": [ + "a" + ], + "targets": [ + "a-end" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532 \ No newline at end of file diff --git a/console-output.txt b/console-output.txt new file mode 100644 index 0000000000..9cac191d43 --- /dev/null +++ b/console-output.txt @@ -0,0 +1,149 @@ +Total messages: 15 (Errors: 0, Warnings: 0) +Returning 6 messages for level "info" + +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-Input-Result", + "sources": [ + "Input" + ], + "targets": [ + "Result" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532 +[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024 +[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772543608757:100 +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchOneEnd) ← [a-branch-default], a-branch-default(branchOneStart) ← [a]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:531 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + }, + { + "id": "a-end", + "width": 275, + "height": 34 + }, + { + "id": "a", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "RIGHT", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "a-branch-default", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [], + "edges": [] + } + ] + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-a-end-Result", + "sources": [ + "a-end" + ], + "targets": [ + "Result" + ] + }, + { + "id": "e-Input-a", + "sources": [ + "Input" + ], + "targets": [ + "a" + ] + }, + { + "id": "e-a-a-end", + "sources": [ + "a" + ], + "targets": [ + "a-end" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543608757:532 \ No newline at end of file diff --git a/console-result.txt b/console-result.txt new file mode 100644 index 0000000000..f3476393ff --- /dev/null +++ b/console-result.txt @@ -0,0 +1,674 @@ +Total messages: 21 (Errors: 0, Warnings: 1) +Returning 10 messages for level "info" + +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [Input]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:531 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-Input-Result", + "sources": [ + "Input" + ], + "targets": [ + "Result" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:532 +[LOG] cancelling request: {reason: saveAndClear, abortController: undefined} @ http://localhost:5183/src/lib/components/copilot/chat/AIChatManager.svelte.ts?t=1772535251800:1024 +[LOG] loadFlow @ http://localhost:5183/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte?t=1772543797839:100 +[LOG] Welcome back admin to testing @ http://localhost:5183/src/routes/(root)/+layout.svelte?t=1772535251800:68 +[LOG] ELK result: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34, + "$H": 277, + "x": 12, + "y": 12 + }, + { + "id": "Input", + "width": 275, + "height": 34, + "$H": 279, + "x": 12, + "y": 108 + }, + { + "id": "Result", + "width": 275, + "height": 34, + "$H": 281, + "x": 12, + "y": 204 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ], + "sections": [ + { + "id": "e-Trigger-Input_s0", + "startPoint": { + "x": 150, + "y": 46 + }, + "endPoint": { + "x": 150, + "y": 108 + }, + "incomingShape": "Trigger", + "outgoingShape": "Input" + } + ], + "container": "root" + }, + { + "id": "e-Input-Result", + "sources": [ + "Input" + ], + "targets": [ + "Result" + ], + "sections": [ + { + "id": "e-Input-Result_s0", + "startPoint": { + "x": 150, + "y": 142 + }, + "endPoint": { + "x": 150, + "y": 204 + }, + "incomingShape": "Input", + "outgoingShape": "Result" + } + ], + "container": "root" + } + ], + "$H": 13, + "x": 0, + "y": 0, + "width": 299, + "height": 250 +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:536 +[WARNING] using deprecated parameters for the initialization function; pass a single object instead @ http://localhost:5183/node_modules/@sveltejs/kit/src/runtime/client/client.js?v=5452fe86:3211 +[LOG] ELK input nodes: [Trigger(trigger) ← [], Input(input2) ← [Trigger], Result(result) ← [a-end], a(module) ← [Input], a-end(branchAllEnd) ← [c,d,g], a-branch-0(branchAllStart) ← [a], b(module) ← [a-branch-0], c(module) ← [b], a-branch-1(branchAllStart) ← [a], d(module) ← [a-branch-1], a-branch-2(branchAllStart) ← [a], e(module) ← [a-branch-2], f(module) ← [e], g(module) ← [f]] @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:531 +[LOG] ELK graph: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34 + }, + { + "id": "Input", + "width": 275, + "height": 34 + }, + { + "id": "Result", + "width": 275, + "height": 34 + }, + { + "id": "a-end", + "width": 275, + "height": 34 + }, + { + "id": "a", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "a-branch-0", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "b", + "width": 275, + "height": 34 + }, + { + "id": "c", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-b-c", + "sources": [ + "b" + ], + "targets": [ + "c" + ] + } + ] + }, + { + "id": "a-branch-1", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "d", + "width": 275, + "height": 34 + } + ], + "edges": [] + }, + { + "id": "a-branch-2", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "e", + "width": 275, + "height": 34 + }, + { + "id": "f", + "width": 275, + "height": 34 + }, + { + "id": "g", + "width": 275, + "height": 34 + } + ], + "edges": [ + { + "id": "e-e-f", + "sources": [ + "e" + ], + "targets": [ + "f" + ] + }, + { + "id": "e-f-g", + "sources": [ + "f" + ], + "targets": [ + "g" + ] + } + ] + } + ] + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ] + }, + { + "id": "e-a-end-Result", + "sources": [ + "a-end" + ], + "targets": [ + "Result" + ] + }, + { + "id": "e-Input-a", + "sources": [ + "Input" + ], + "targets": [ + "a" + ] + }, + { + "id": "e-a-a-end", + "sources": [ + "a" + ], + "targets": [ + "a-end" + ] + } + ] +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:532 +[LOG] ELK result: { + "id": "root", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50", + "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", + "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES" + }, + "children": [ + { + "id": "Trigger", + "width": 275, + "height": 34, + "$H": 352, + "x": 36, + "y": 12 + }, + { + "id": "Input", + "width": 275, + "height": 34, + "$H": 354, + "x": 36, + "y": 108 + }, + { + "id": "Result", + "width": 275, + "height": 34, + "$H": 356, + "x": 36, + "y": 888 + }, + { + "id": "a-end", + "width": 275, + "height": 34, + "$H": 358, + "x": 36, + "y": 792 + }, + { + "id": "a", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "a-branch-0", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "b", + "width": 275, + "height": 34, + "$H": 364, + "x": 12, + "y": 12 + }, + { + "id": "c", + "width": 275, + "height": 34, + "$H": 366, + "x": 12, + "y": 108 + } + ], + "edges": [ + { + "id": "e-b-c", + "sources": [ + "b" + ], + "targets": [ + "c" + ], + "sections": [ + { + "id": "e-b-c_s0", + "startPoint": { + "x": 149.5, + "y": 46 + }, + "endPoint": { + "x": 149.5, + "y": 108 + }, + "incomingShape": "b", + "outgoingShape": "c" + } + ], + "container": "a-branch-0" + } + ], + "$H": 362, + "x": 12, + "y": 90, + "width": 299, + "height": 154 + }, + { + "id": "a-branch-1", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "d", + "width": 275, + "height": 34, + "$H": 370, + "x": 12, + "y": 12 + } + ], + "edges": [], + "$H": 368, + "x": 12, + "y": 12, + "width": 299, + "height": 58 + }, + { + "id": "a-branch-2", + "layoutOptions": { + "elk.algorithm": "layered", + "elk.direction": "DOWN", + "elk.layered.spacing.nodeNodeBetweenLayers": "62", + "elk.spacing.nodeNode": "50" + }, + "children": [ + { + "id": "e", + "width": 275, + "height": 34, + "$H": 374, + "x": 12, + "y": 12 + }, + { + "id": "f", + "width": 275, + "height": 34, + "$H": 376, + "x": 12, + "y": 108 + }, + { + "id": "g", + "width": 275, + "height": 34, + "$H": 378, + "x": 12, + "y": 204 + } + ], + "edges": [ + { + "id": "e-e-f", + "sources": [ + "e" + ], + "targets": [ + "f" + ], + "sections": [ + { + "id": "e-e-f_s0", + "startPoint": { + "x": 149.5, + "y": 46 + }, + "endPoint": { + "x": 149.5, + "y": 108 + }, + "incomingShape": "e", + "outgoingShape": "f" + } + ], + "container": "a-branch-2" + }, + { + "id": "e-f-g", + "sources": [ + "f" + ], + "targets": [ + "g" + ], + "sections": [ + { + "id": "e-f-g_s0", + "startPoint": { + "x": 149.5, + "y": 142 + }, + "endPoint": { + "x": 149.5, + "y": 204 + }, + "incomingShape": "f", + "outgoingShape": "g" + } + ], + "container": "a-branch-2" + } + ], + "$H": 372, + "x": 12, + "y": 264, + "width": 299, + "height": 250 + } + ], + "$H": 360, + "x": 12, + "y": 204, + "width": 323, + "height": 526 + } + ], + "edges": [ + { + "id": "e-Trigger-Input", + "sources": [ + "Trigger" + ], + "targets": [ + "Input" + ], + "sections": [ + { + "id": "e-Trigger-Input_s0", + "startPoint": { + "x": 174, + "y": 46 + }, + "endPoint": { + "x": 174, + "y": 108 + }, + "incomingShape": "Trigger", + "outgoingShape": "Input" + } + ], + "container": "root" + }, + { + "id": "e-a-end-Result", + "sources": [ + "a-end" + ], + "targets": [ + "Result" + ], + "sections": [ + { + "id": "e-a-end-Result_s0", + "startPoint": { + "x": 174, + "y": 826 + }, + "endPoint": { + "x": 174, + "y": 888 + }, + "incomingShape": "a-end", + "outgoingShape": "Result" + } + ], + "container": "root" + }, + { + "id": "e-Input-a", + "sources": [ + "Input" + ], + "targets": [ + "a" + ], + "sections": [ + { + "id": "e-Input-a_s0", + "startPoint": { + "x": 174, + "y": 142 + }, + "endPoint": { + "x": 174, + "y": 204 + }, + "incomingShape": "Input", + "outgoingShape": "a" + } + ], + "container": "root" + }, + { + "id": "e-a-a-end", + "sources": [ + "a" + ], + "targets": [ + "a-end" + ], + "sections": [ + { + "id": "e-a-a-end_s0", + "startPoint": { + "x": 174, + "y": 730 + }, + "endPoint": { + "x": 174, + "y": 792 + }, + "incomingShape": "a", + "outgoingShape": "a-end" + } + ], + "container": "root" + } + ], + "$H": 350, + "x": 0, + "y": 0, + "width": 347, + "height": 934 +} @ http://localhost:5183/src/lib/components/graph/FlowGraphV2.svelte?t=1772543797839:536 \ No newline at end of file diff --git a/flow-layout-after-fix.png b/flow-layout-after-fix.png new file mode 100644 index 0000000000..27a560905a Binary files /dev/null and b/flow-layout-after-fix.png differ diff --git a/flow-layout-bk-leftup.png b/flow-layout-bk-leftup.png new file mode 100644 index 0000000000..a669cbe467 Binary files /dev/null and b/flow-layout-bk-leftup.png differ diff --git a/flow-layout-branch-all-regression.png b/flow-layout-branch-all-regression.png new file mode 100644 index 0000000000..e05f863db3 Binary files /dev/null and b/flow-layout-branch-all-regression.png differ diff --git a/flow-layout-branchall-3branches.png b/flow-layout-branchall-3branches.png new file mode 100644 index 0000000000..b6cc9defe0 Binary files /dev/null and b/flow-layout-branchall-3branches.png differ diff --git a/flow-layout-branchall-direction-down.png b/flow-layout-branchall-direction-down.png new file mode 100644 index 0000000000..0a6e24cf7a Binary files /dev/null and b/flow-layout-branchall-direction-down.png differ diff --git a/flow-layout-branchone.png b/flow-layout-branchone.png new file mode 100644 index 0000000000..cfd6d0dd7f Binary files /dev/null and b/flow-layout-branchone.png differ diff --git a/flow-layout-final.png b/flow-layout-final.png new file mode 100644 index 0000000000..6b9b7c23d4 Binary files /dev/null and b/flow-layout-final.png differ diff --git a/flow-layout-fitview-topalign.png b/flow-layout-fitview-topalign.png new file mode 100644 index 0000000000..0e1741e41d Binary files /dev/null and b/flow-layout-fitview-topalign.png differ diff --git a/flow-layout-fitview.png b/flow-layout-fitview.png new file mode 100644 index 0000000000..86bec17f1a Binary files /dev/null and b/flow-layout-fitview.png differ diff --git a/flow-layout-fix-v3-fitview.png b/flow-layout-fix-v3-fitview.png new file mode 100644 index 0000000000..a24241ce80 Binary files /dev/null and b/flow-layout-fix-v3-fitview.png differ diff --git a/flow-layout-fix-v3.png b/flow-layout-fix-v3.png new file mode 100644 index 0000000000..853bf4b4c1 Binary files /dev/null and b/flow-layout-fix-v3.png differ diff --git a/flow-layout-group-compounds.png b/flow-layout-group-compounds.png new file mode 100644 index 0000000000..f39e458b1a Binary files /dev/null and b/flow-layout-group-compounds.png differ diff --git a/flow-layout-hierarchical-v1.png b/flow-layout-hierarchical-v1.png new file mode 100644 index 0000000000..5d08bd4a1e Binary files /dev/null and b/flow-layout-hierarchical-v1.png differ diff --git a/flow-layout-modelorder.png b/flow-layout-modelorder.png new file mode 100644 index 0000000000..a669cbe467 Binary files /dev/null and b/flow-layout-modelorder.png differ diff --git a/flow-layout-nodewidths-fitview.png b/flow-layout-nodewidths-fitview.png new file mode 100644 index 0000000000..b0bf6cdf0d Binary files /dev/null and b/flow-layout-nodewidths-fitview.png differ diff --git a/flow-layout-nodewidths-fix.png b/flow-layout-nodewidths-fix.png new file mode 100644 index 0000000000..aa54414597 Binary files /dev/null and b/flow-layout-nodewidths-fix.png differ diff --git a/flow-layout-restored.png b/flow-layout-restored.png new file mode 100644 index 0000000000..2f6dee18af Binary files /dev/null and b/flow-layout-restored.png differ diff --git a/flow-layout-separateCC.png b/flow-layout-separateCC.png new file mode 100644 index 0000000000..184ace8b52 Binary files /dev/null and b/flow-layout-separateCC.png differ diff --git a/flow-layout-swim-padding.png b/flow-layout-swim-padding.png new file mode 100644 index 0000000000..db4ace0e07 Binary files /dev/null and b/flow-layout-swim-padding.png differ diff --git a/flow-layout-topalign.png b/flow-layout-topalign.png new file mode 100644 index 0000000000..5ba8241404 Binary files /dev/null and b/flow-layout-topalign.png differ diff --git a/flow-layout-with-postprocess-v2.png b/flow-layout-with-postprocess-v2.png new file mode 100644 index 0000000000..981ca3f4dd Binary files /dev/null and b/flow-layout-with-postprocess-v2.png differ diff --git a/flow-layout-with-postprocess.png b/flow-layout-with-postprocess.png new file mode 100644 index 0000000000..55b8171542 Binary files /dev/null and b/flow-layout-with-postprocess.png differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 520148b1d6..c24e48e02d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.648.0", + "version": "1.649.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.648.0", + "version": "1.649.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 9232f07db8..d6c6554459 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.648.0", + "version": "1.649.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/frontend/src/lib/components/DBManagerDrawer.svelte b/frontend/src/lib/components/DBManagerDrawer.svelte index c5986e18a4..5996d98075 100644 --- a/frontend/src/lib/components/DBManagerDrawer.svelte +++ b/frontend/src/lib/components/DBManagerDrawer.svelte @@ -6,27 +6,20 @@ import DrawerContent from './common/drawer/DrawerContent.svelte' import Select from './select/Select.svelte' import { ArrowLeft, Expand, LoaderCircle, Minimize, RefreshCcw } from 'lucide-svelte' - import type { DbInput } from './dbTypes' import DBManagerContent from './DBManagerContent.svelte' import { resource } from 'runed' + import { untrack } from 'svelte' + import type { DbManagerUriState } from './dbManagerDrawerModel.svelte' interface Props { + uriState: DbManagerUriState /** Z-index offset for the drawer, useful when opening from within modals */ offset?: number } - let { offset = 0 }: Props = $props() + let { uriState, offset = 0 }: Props = $props() - let input: DbInput | undefined = $state() - let open = $derived(!!input) - - // For datatable inputs, track the selected datatable separately - let selectedDatatable = $state(undefined) - - // Check if input is a datatable type - const isDatatableInput = $derived( - input?.type === 'database' && input.resourcePath.startsWith('datatable://') - ) + let open = $derived(uriState.open) // Load available datatables when drawer opens with datatable input const datatables = resource([], async () => { @@ -39,16 +32,6 @@ } }) - // Computed input that updates when selectedDatatable changes - const effectiveInput: DbInput | undefined = $derived.by(() => { - if (!input) return undefined - if (!isDatatableInput || !selectedDatatable) return input - return { - ...input, - resourcePath: `datatable://${selectedDatatable}` - } - }) - const datatableItems = $derived( datatables.current.map((dt) => ({ value: dt, @@ -56,32 +39,26 @@ })) ) - export function openDrawer(nInput: DbInput) { - input = nInput - if (isDatatableInput) { - datatables.refetch() + // Refetch datatables when switching to a datatable input + $effect(() => { + if (uriState.isDatatableInput) { + untrack(() => datatables.refetch()) } - // If it's a datatable input, extract the datatable name for the selector - if (nInput.type === 'database' && nInput.resourcePath.startsWith('datatable://')) { - selectedDatatable = nInput.resourcePath.replace('datatable://', '') - datatables.refetch() - } else { - selectedDatatable = undefined - } - } - export function closeDrawer() { - input = undefined - selectedDatatable = undefined + }) + + function handleClose() { + uriState.closeDrawer() dbManagerContent?.clearReplResult() - if (window.location.hash.startsWith('#dbmanager:')) - history.replaceState('', document.title, window.location.href.replace(/#dbmanager:.*$/, '')) } let windowWidth = $state(window.innerWidth) let expand = $state(false) $effect(() => { - if (!open) expand = false + if (!open) { + expand = false + uriState.closeDrawer() + } }) let dbManagerContent: DBManagerContent | undefined = $state() @@ -96,7 +73,7 @@ size={expand ? `${windowWidth}px` : '1200px'} preventEscape {offset} - on:close={closeDrawer} + on:close={handleClose} > - {#if effectiveInput && $workspaceStore} - {#key selectedDatatable} - + {#if uriState.effectiveInput && $workspaceStore} + {#key uriState.selectedDatatable} + {#snippet dbSelector()} - {#if isDatatableInput} + {#if uriState.isDatatableInput} {#if datatables.loading}
@@ -125,7 +108,7 @@ +
+ + +
+ + + + + +
+ + + + + diff --git a/lsp/Pipfile b/lsp/Pipfile index c01fab216e..37aecedea0 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.648.0" +wmill = ">=1.649.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 9f9ee14ffb..2b181a6c7b 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.648.0 + version: 1.649.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 53f6b21227..ec7cea297d 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.648.0' + ModuleVersion = '1.649.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 0d6daad51f..368b0de11b 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.648.0" +version = "1.649.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/typescript-client/jsr.json b/typescript-client/jsr.json index e4842b836b..8b07a94942 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.648.0", + "version": "1.649.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 71689ffd27..a5cd2f4395 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.648.0", + "version": "1.649.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "sideEffects": false, diff --git a/version.txt b/version.txt index 53ea075c24..c34844244a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.648.0 +1.649.0