Compare commits
2 Commits
v1.673.0
...
folder-dep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba775edef2 | ||
|
|
91f0a564b9 |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,32 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.673.0](https://github.com/windmill-labs/windmill/compare/v1.672.0...v1.673.0) (2026-04-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add endpoint to restart workers in a worker group ([#8659](https://github.com/windmill-labs/windmill/issues/8659)) ([f0437eb](https://github.com/windmill-labs/windmill/commit/f0437eba1925a9aa4c430008027d637a0c89ee39))
|
||||
* add Entra ID (Azure Workload Identity) database auth ([#8526](https://github.com/windmill-labs/windmill/issues/8526)) ([6a5cfbc](https://github.com/windmill-labs/windmill/commit/6a5cfbc159a0ad7925fd7ce5eefc8eaa21bbb70b))
|
||||
* add LIMIT_WINDOWS_TO_1CU env var for Windows worker memory limits ([#8681](https://github.com/windmill-labs/windmill/issues/8681)) ([d2d6810](https://github.com/windmill-labs/windmill/commit/d2d6810db954114f3333853bd3476cb8fc735f92))
|
||||
* restore bun for dedicated workers, fix dispatch & serialization, cross-workspace deps ([#8645](https://github.com/windmill-labs/windmill/issues/8645)) ([619ebb6](https://github.com/windmill-labs/windmill/commit/619ebb65ce8dce8264add31c3147919802a8286a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add HMAC signature verification to Slack interactive callback endpoint ([#8611](https://github.com/windmill-labs/windmill/issues/8611)) ([55e8a5c](https://github.com/windmill-labs/windmill/commit/55e8a5cff1f185b1dbd332d37b877972efa1ed7d))
|
||||
* correct raw app flow inputs ([#8667](https://github.com/windmill-labs/windmill/issues/8667)) ([28c0730](https://github.com/windmill-labs/windmill/commit/28c073056c65d4ed1600e39679497e5af964347f))
|
||||
* pass selected language to AI agent when generating flow scripts ([#8680](https://github.com/windmill-labs/windmill/issues/8680)) ([381011a](https://github.com/windmill-labs/windmill/commit/381011a4a8e48454e9c146c64db502293e646b99))
|
||||
* poll for preview results to avoid undici headers timeout ([#8682](https://github.com/windmill-labs/windmill/issues/8682)) ([ff5fa9f](https://github.com/windmill-labs/windmill/commit/ff5fa9f64fe4aaf33e06b20f02373894b5df0f95))
|
||||
* pre-fix trigger edited_by for superadmins not in workspace ([#8669](https://github.com/windmill-labs/windmill/issues/8669)) ([350ffdc](https://github.com/windmill-labs/windmill/commit/350ffdce297ba5b84f9dd247eede6da0c6b0956c))
|
||||
* resolve race condition where flow sync push reverts to stale version ([#8673](https://github.com/windmill-labs/windmill/issues/8673)) ([d569e9e](https://github.com/windmill-labs/windmill/commit/d569e9e29c588243a90b1cd25f866efb0d178640))
|
||||
* respect disabled fields in JSON input mode ([#8663](https://github.com/windmill-labs/windmill/issues/8663)) ([7fd0bf9](https://github.com/windmill-labs/windmill/commit/7fd0bf974d2ba2644bb01dd5e9ddc84749e166f5))
|
||||
* Run typed pg queries in a single protocol conversation ([#8679](https://github.com/windmill-labs/windmill/issues/8679)) ([8581a33](https://github.com/windmill-labs/windmill/commit/8581a3300d056040b7e3ab77d629c74f034c9c97))
|
||||
* sanitize MCP tool schemas for JSON Schema draft 2020-12 compliance ([#8666](https://github.com/windmill-labs/windmill/issues/8666)) ([8c3c97f](https://github.com/windmill-labs/windmill/commit/8c3c97f7a670d47019cc666219f8187f48499672))
|
||||
* skip generate-metadata confirmation prompt in non-interactive CI ([#8678](https://github.com/windmill-labs/windmill/issues/8678)) ([39af1b7](https://github.com/windmill-labs/windmill/commit/39af1b75afc8458f85dec4fe51dfaed3d0cb000d))
|
||||
* strip f/ prefix from folder paths when deploying from workspace forks ([#8662](https://github.com/windmill-labs/windmill/issues/8662)) ([7ab0ea5](https://github.com/windmill-labs/windmill/commit/7ab0ea581d349fbfdb56d22cf9903a90efa045bb))
|
||||
* support branch-specific folder.meta.yaml in missing-meta check ([#8661](https://github.com/windmill-labs/windmill/issues/8661)) ([c87a6a0](https://github.com/windmill-labs/windmill/commit/c87a6a0f2c1346bf5e21f128d32d89bdca039243))
|
||||
* validate rd redirect on login with same rules as logout ([#8655](https://github.com/windmill-labs/windmill/issues/8655)) ([bcce627](https://github.com/windmill-labs/windmill/commit/bcce62738791a4e9b9f4dbc64731eef163230172))
|
||||
|
||||
## [1.672.0](https://github.com/windmill-labs/windmill/compare/v1.671.0...v1.672.0) (2026-04-01)
|
||||
|
||||
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE flow SET versions = array_append(versions, $1) WHERE path = $2 AND workspace_id = $3 AND (versions[array_upper(versions, 1)] IS DISTINCT FROM $1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "32f26d69b15a74fc96dd585f9eb3c998447ca8b97894bb1a2b238f984c1dc588"
|
||||
}
|
||||
@@ -33,8 +33,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
@@ -148,8 +148,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO notify_event (channel, payload) VALUES ('restart_worker_group', $1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "78f7c4efd7be2aad03f0ec67ab882e1f9aaa7141ab75046a71ac789ab659bdc6"
|
||||
}
|
||||
@@ -76,8 +76,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +153,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +118,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +83,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,8 +133,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +153,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +123,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value AS \"value!: Json<Box<RawValue>>\" FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value!: Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c569535d4aded82ffef5e5066e24948c8fcdb8d36913775449b982782c8583e9"
|
||||
}
|
||||
@@ -68,8 +68,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,8 +133,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -85,8 +84,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
"nu",
|
||||
"java",
|
||||
"duckdb",
|
||||
"ruby",
|
||||
"rlang"
|
||||
"ruby"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
296
backend/Cargo.lock
generated
296
backend/Cargo.lock
generated
@@ -2108,7 +2108,7 @@ dependencies = [
|
||||
"rayon",
|
||||
"safetensors",
|
||||
"thiserror 2.0.18",
|
||||
"yoke 0.8.2",
|
||||
"yoke 0.8.1",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -2373,9 +2373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cmov"
|
||||
version = "0.5.3"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
|
||||
checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
@@ -2805,9 +2805,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctutils"
|
||||
version = "0.4.2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
|
||||
checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4"
|
||||
dependencies = [
|
||||
"cmov",
|
||||
]
|
||||
@@ -7224,23 +7224,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke 0.8.2",
|
||||
"yoke 0.8.1",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
@@ -7251,9 +7250,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
@@ -7265,15 +7264,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.2.0"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
@@ -7285,20 +7284,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.2.0"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke 0.8.2",
|
||||
"yoke 0.8.1",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
@@ -8047,9 +8046,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.184"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "libffi"
|
||||
@@ -8205,9 +8204,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
@@ -10428,9 +10427,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
@@ -12118,15 +12117,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
|
||||
|
||||
[[package]]
|
||||
name = "scc"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
|
||||
dependencies = [
|
||||
"sdd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
@@ -12232,12 +12222,6 @@ dependencies = [
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdd"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
@@ -12574,32 +12558,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
|
||||
dependencies = [
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"scc",
|
||||
"serial_test_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@@ -14277,9 +14235,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
@@ -14667,9 +14625,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9"
|
||||
dependencies = [
|
||||
"winnow 1.0.1",
|
||||
]
|
||||
@@ -15983,7 +15941,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16012,7 +15970,6 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_yml",
|
||||
"serial_test",
|
||||
"sha1",
|
||||
"sha2 0.10.9",
|
||||
"sql-builder",
|
||||
@@ -16062,7 +16019,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16075,7 +16032,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -16216,7 +16173,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16239,7 +16196,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16252,7 +16209,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16278,7 +16235,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -16288,7 +16245,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16305,7 +16262,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"base64 0.22.1",
|
||||
@@ -16328,7 +16285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16351,7 +16308,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16367,7 +16324,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16387,7 +16344,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16407,7 +16364,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16421,7 +16378,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16452,7 +16409,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16477,7 +16434,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"flate2",
|
||||
@@ -16495,7 +16452,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16517,7 +16474,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16537,7 +16494,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16567,7 +16524,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16594,7 +16551,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -16606,7 +16563,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.8.4",
|
||||
@@ -16631,7 +16588,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16645,7 +16602,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"axum 0.8.4",
|
||||
"chrono",
|
||||
@@ -16677,7 +16634,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -16691,7 +16648,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.4",
|
||||
@@ -16710,7 +16667,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"aho-corasick",
|
||||
@@ -16812,7 +16769,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@@ -16831,7 +16788,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -16846,7 +16803,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -16870,7 +16827,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16887,7 +16844,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -16903,7 +16860,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16924,7 +16881,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16955,7 +16912,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-oauth2",
|
||||
@@ -16979,7 +16936,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -17013,7 +16970,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -17031,7 +16988,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -17040,7 +16997,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17052,7 +17009,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -17064,7 +17021,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -17076,7 +17033,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17088,7 +17045,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -17100,7 +17057,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -17111,7 +17068,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -17122,7 +17079,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -17134,7 +17091,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -17145,7 +17102,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17167,7 +17124,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -17179,7 +17136,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17193,7 +17150,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -17210,7 +17167,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17223,7 +17180,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -17235,7 +17192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -17253,7 +17210,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -17269,7 +17226,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -17285,7 +17242,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -17296,7 +17253,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17333,7 +17290,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -17371,7 +17328,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"wasm-bindgen",
|
||||
@@ -17382,7 +17339,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17411,7 +17368,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17435,7 +17392,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17468,7 +17425,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17488,7 +17445,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17522,7 +17479,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17558,7 +17515,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17581,7 +17538,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17605,7 +17562,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -17629,7 +17586,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17664,7 +17621,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17692,7 +17649,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17715,7 +17672,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
@@ -17734,7 +17691,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
@@ -17838,14 +17795,13 @@ dependencies = [
|
||||
"windmill-runtime-nativets",
|
||||
"windmill-types",
|
||||
"windmill-worker-volumes",
|
||||
"windows 0.61.3",
|
||||
"x509-parser 0.16.0",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker-volumes"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
@@ -18545,9 +18501,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "wtf8"
|
||||
@@ -18680,12 +18636,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
|
||||
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive 0.8.2",
|
||||
"yoke-derive 0.8.1",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
@@ -18703,9 +18659,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -18735,18 +18691,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -18776,31 +18732,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke 0.8.2",
|
||||
"yoke 0.8.1",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.6"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
|
||||
dependencies = [
|
||||
"yoke 0.8.2",
|
||||
"yoke 0.8.1",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.3"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -85,7 +85,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
57dd88faa3b0b354f813385cf3f6a34eca54a4a1
|
||||
e08a87450627bef9013498e40ee93a47bedda7ee
|
||||
|
||||
@@ -41,69 +41,50 @@ pub async fn connect_db(
|
||||
};
|
||||
|
||||
let pool = connect(database_url.clone(), max_connections, worker_mode).await?;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
{
|
||||
let needs_token_refresh = matches!(
|
||||
database_url,
|
||||
DatabaseUrl::IamRds(_) | DatabaseUrl::EntraId(_)
|
||||
);
|
||||
let label = match &database_url {
|
||||
DatabaseUrl::IamRds(_) => "IAM RDS",
|
||||
DatabaseUrl::EntraId(_) => "Entra ID",
|
||||
DatabaseUrl::Static(_) => "",
|
||||
};
|
||||
if needs_token_refresh {
|
||||
let pool2 = pool.clone();
|
||||
let database_url2 = database_url.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = killpill_rx.recv() => {
|
||||
break;
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
|
||||
if !database_url2.needs_refresh().await {
|
||||
continue;
|
||||
}
|
||||
let new_url = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(10),
|
||||
get_database_url(),
|
||||
)
|
||||
.await;
|
||||
let pool2 = pool.clone();
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
if let DatabaseUrl::IamRds(database_url) = database_url {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = killpill_rx.recv() => {
|
||||
break;
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
|
||||
let needs_refresh = {
|
||||
let read_guard = database_url.read().await;
|
||||
read_guard.needs_refresh()
|
||||
};
|
||||
if needs_refresh {
|
||||
let new_url = tokio::time::timeout(std::time::Duration::from_secs(10), get_database_url()).await;
|
||||
match new_url {
|
||||
Ok(Ok(new_url)) => {
|
||||
match new_url.connect_options().await {
|
||||
Ok(connect_options) => {
|
||||
pool2.set_connect_options(connect_options);
|
||||
tracing::info!("Refreshed {label} URL successfully");
|
||||
tracing::info!("Refreshed IAM RDS URL successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Error getting {label} connect options, retrying in 10s: {e}"
|
||||
);
|
||||
tracing::error!("Error getting IAM RDS connect options, retrying in 10s: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::error!(
|
||||
"Error refreshing {label} URL, trying again in 10s: {e}"
|
||||
);
|
||||
tracing::error!("Error refreshing IAM RDS URL, trying again in 10s: {}", e);
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Timeout after 10s refreshing {label} URL, trying again in 10s: {e}"
|
||||
);
|
||||
tracing::error!("Timeout after 10s refreshing IAM RDS URL, trying again in 10 seconds: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(pool)
|
||||
|
||||
@@ -1597,12 +1597,6 @@ async fn process_notify_event(
|
||||
tracing::debug!("config changed but did not target this server/worker");
|
||||
}
|
||||
}
|
||||
"restart_worker_group" => {
|
||||
if worker_mode && payload == *WORKER_GROUP {
|
||||
tracing::info!("Restart requested for worker group '{payload}'");
|
||||
spawn_graceful_killpill(tx, db, 10, "worker group restart requested").await;
|
||||
}
|
||||
}
|
||||
"notify_webhook_change" => {
|
||||
tracing::info!(
|
||||
"Webhook change detected, invalidating webhook cache: {}",
|
||||
|
||||
@@ -429,59 +429,6 @@ export function main() {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_bun_job_syntax_error_unclosed_bracket(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// Reproduces the "Unexpected end of file at main.ts:0" error reported
|
||||
// when a TS file has a missing closing bracket — Bun's bundler gives no
|
||||
// useful location info.
|
||||
let content = r#"
|
||||
export async function main() {
|
||||
if (true) {
|
||||
return "hello";
|
||||
// missing closing bracket for the function
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
let job = JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
language: ScriptLang::Bun,
|
||||
lock: None,
|
||||
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,
|
||||
dedicated_worker: None,
|
||||
modules: None,
|
||||
});
|
||||
|
||||
let completed = run_job_in_new_worker_until_complete(&db, false, job, port).await;
|
||||
|
||||
assert!(!completed.success);
|
||||
let result = completed
|
||||
.result
|
||||
.as_ref()
|
||||
.and_then(|v| v.get("error"))
|
||||
.and_then(|v| v.get("message"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
assert!(
|
||||
result.contains("Unexpected end of file"),
|
||||
"should contain bun parser error, got: {result}"
|
||||
);
|
||||
assert!(
|
||||
result.contains("syntax error"),
|
||||
"should contain helpful hint about syntax errors, got: {result}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Annotation Mode Tests
|
||||
// ============================================================================
|
||||
|
||||
@@ -1107,7 +1107,6 @@ main <- function(msg) {
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
.arg("msg", json!("world"))
|
||||
.run_until_complete(&db, false, port)
|
||||
@@ -1145,7 +1144,6 @@ main <- function() {
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
@@ -1182,7 +1180,6 @@ main <- function() {
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
modules: None,
|
||||
}))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await
|
||||
|
||||
@@ -12,7 +12,9 @@ use std::{collections::HashMap, time::Duration};
|
||||
mod ee;
|
||||
pub mod ee_oss;
|
||||
|
||||
use windmill_api_auth::{require_devops_role, require_super_admin, ApiAuthed};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_api_auth::require_devops_role;
|
||||
use windmill_api_auth::{require_super_admin, ApiAuthed};
|
||||
use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT;
|
||||
use windmill_common::DB;
|
||||
|
||||
@@ -100,10 +102,6 @@ pub fn global_service() -> Router {
|
||||
.route(
|
||||
"/sync_cached_resource_types",
|
||||
post(sync_cached_resource_types),
|
||||
)
|
||||
.route(
|
||||
"/restart_worker_group/{worker_group}",
|
||||
post(restart_worker_group),
|
||||
);
|
||||
|
||||
// Vault integration routes (EE only - requires both private and enterprise features)
|
||||
@@ -664,25 +662,6 @@ pub async fn send_stats(Extension(db): Extension<DB>, authed: ApiAuthed) -> Resu
|
||||
Ok("Sent stats".to_string())
|
||||
}
|
||||
|
||||
async fn restart_worker_group(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
Path(worker_group): Path<String>,
|
||||
) -> error::Result<String> {
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO notify_event (channel, payload) VALUES ('restart_worker_group', $1)",
|
||||
worker_group
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(format!(
|
||||
"Restart signal sent to worker group '{worker_group}'"
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct StatsDownload {
|
||||
pub signature: String,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.673.0
|
||||
version: 1.672.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -1370,28 +1370,6 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/settings/restart_worker_group/{worker_group}:
|
||||
post:
|
||||
summary: restart worker group
|
||||
description: Send a restart signal to all workers in the specified worker group. Workers will gracefully shut down and are expected to be restarted by their supervisor. Requires devops role.
|
||||
operationId: restartWorkerGroup
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
- name: worker_group
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: the name of the worker group to restart
|
||||
responses:
|
||||
"200":
|
||||
description: restart signal sent
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/settings/get_stats:
|
||||
get:
|
||||
summary: get telemetry stats with HMAC signature (EE only)
|
||||
|
||||
@@ -159,24 +159,13 @@ pub async fn check_nb_of_user(db: &DB) -> error::Result<()> {
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub struct SlackVerifier {
|
||||
mac: HmacSha256,
|
||||
_mac: HmacSha256,
|
||||
}
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl SlackVerifier {
|
||||
pub fn new<S: AsRef<[u8]>>(secret: S) -> anyhow::Result<SlackVerifier> {
|
||||
HmacSha256::new_from_slice(secret.as_ref())
|
||||
.map(|mac| SlackVerifier { mac })
|
||||
.map(|mac| SlackVerifier { _mac: mac })
|
||||
.map_err(|_| anyhow::anyhow!("invalid secret"))
|
||||
}
|
||||
|
||||
pub fn verify(&self, ts: &str, body: &str, exp_sig: &str) -> anyhow::Result<()> {
|
||||
let basestring = format!("v0:{}:{}", ts, body);
|
||||
let mut mac = self.mac.clone();
|
||||
mac.update(basestring.as_bytes());
|
||||
let sig = format!("v0={}", hex::encode(mac.finalize().into_bytes()));
|
||||
if sig != exp_sig {
|
||||
Err(anyhow::anyhow!("signature mismatch"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
extract::{Form, Path, Query},
|
||||
Extension,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::HeaderMap;
|
||||
use hyper::StatusCode;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -121,41 +119,12 @@ struct PrivateMetadata {
|
||||
hide_cancel: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "oauth2")]
|
||||
fn verify_slack_callback_signature(headers: &HeaderMap, body: &str) -> Result<(), Error> {
|
||||
if let Some(sv) = crate::SLACK_SIGNING_SECRET.as_ref() {
|
||||
let sig = headers
|
||||
.get("X-Slack-Signature")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("");
|
||||
let ts = headers
|
||||
.get("X-Slack-Request-Timestamp")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("");
|
||||
sv.verify(ts, body, sig)
|
||||
.map_err(|_| Error::BadRequest("Slack signature verification failed".to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "oauth2"))]
|
||||
fn verify_slack_callback_signature(_headers: &HeaderMap, _body: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn slack_app_callback_handler(
|
||||
authed: Option<ApiAuthed>,
|
||||
opt_tokened: OptTokened,
|
||||
Extension(db): Extension<DB>,
|
||||
headers: HeaderMap,
|
||||
body: Bytes,
|
||||
Form(form_data): Form<SlackFormData>,
|
||||
) -> Result<StatusCode, Error> {
|
||||
let body_str = String::from_utf8_lossy(&body);
|
||||
verify_slack_callback_signature(&headers, &body_str)?;
|
||||
|
||||
let form_data: SlackFormData = serde_urlencoded::from_bytes(&body)
|
||||
.map_err(|e| Error::BadRequest(format!("invalid form data: {}", e)))?;
|
||||
|
||||
tracing::debug!("Form data: {:#?}", form_data);
|
||||
let payload: Payload = serde_json::from_str(&form_data.payload)?;
|
||||
tracing::debug!("Payload: {:#?}", payload);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use anyhow::Result;
|
||||
|
||||
/// Parsed database connection parameters, shared across DB auth providers (IAM RDS, Entra ID, etc.)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DatabaseParams {
|
||||
pub hostname: String,
|
||||
pub port: u64,
|
||||
pub username: String,
|
||||
pub database: String,
|
||||
}
|
||||
|
||||
/// Extract database connection parameters from a PostgreSQL URL
|
||||
pub fn extract_database_params(database_url: &str) -> Result<DatabaseParams> {
|
||||
let url = url::Url::parse(database_url)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse database URL: {}", e))?;
|
||||
|
||||
let hostname = url
|
||||
.host_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Database URL missing hostname"))?
|
||||
.to_string();
|
||||
|
||||
let port = url.port().unwrap_or(5432) as u64;
|
||||
|
||||
let username = if url.username().is_empty() {
|
||||
return Err(anyhow::anyhow!("Database URL missing username"));
|
||||
} else {
|
||||
urlencoding::decode(url.username())?.to_string()
|
||||
};
|
||||
|
||||
let database = url
|
||||
.path()
|
||||
.trim_start_matches('/')
|
||||
.split('/')
|
||||
.next()
|
||||
.filter(|s| !s.is_empty())
|
||||
.ok_or_else(|| anyhow::anyhow!("Database URL missing database name"))?
|
||||
.to_string();
|
||||
|
||||
Ok(DatabaseParams {
|
||||
hostname,
|
||||
port,
|
||||
username,
|
||||
database: urlencoding::decode(&database)?.to_string(),
|
||||
})
|
||||
}
|
||||
@@ -43,10 +43,7 @@ pub mod cache;
|
||||
pub mod client;
|
||||
pub mod db;
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
mod db_entra_ee;
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
mod db_iam_ee;
|
||||
pub mod db_params;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod ee;
|
||||
pub mod ee_oss;
|
||||
@@ -636,14 +633,12 @@ impl PgDatabase {
|
||||
pub enum DatabaseUrl {
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
IamRds(std::sync::Arc<tokio::sync::RwLock<db_iam_ee::IamRdsUrl>>),
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
EntraId(std::sync::Arc<tokio::sync::RwLock<db_entra_ee::EntraIdUrl>>),
|
||||
Static(String),
|
||||
}
|
||||
|
||||
impl DatabaseUrl {
|
||||
/// Get the database URL as a string.
|
||||
/// For token-based auth, this returns the original URL (for metadata extraction).
|
||||
/// Note: For IAM RDS, this returns the original URL (for metadata extraction).
|
||||
/// For actual database connections, use connect_options() instead.
|
||||
pub async fn as_str(&self) -> String {
|
||||
match self {
|
||||
@@ -652,18 +647,13 @@ impl DatabaseUrl {
|
||||
let guard = rds_url.read().await;
|
||||
guard.as_str().to_string()
|
||||
}
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
DatabaseUrl::EntraId(entra_url) => {
|
||||
let guard = entra_url.read().await;
|
||||
guard.as_str().to_string()
|
||||
}
|
||||
DatabaseUrl::Static(url) => url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get PgConnectOptions for this database URL.
|
||||
/// For token-based auth (IAM RDS, Entra ID), this returns options built directly from the
|
||||
/// token to avoid double-encoding issues with temporary credentials.
|
||||
/// For IAM RDS, this returns options built directly from the token to avoid double-encoding
|
||||
/// issues with temporary credentials (IRSA/Pod Identity).
|
||||
/// For static URLs, this parses the URL string.
|
||||
pub async fn connect_options(&self) -> Result<sqlx::postgres::PgConnectOptions, Error> {
|
||||
match self {
|
||||
@@ -672,11 +662,6 @@ impl DatabaseUrl {
|
||||
let guard = rds_url.read().await;
|
||||
Ok(guard.connect_options())
|
||||
}
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
DatabaseUrl::EntraId(entra_url) => {
|
||||
let guard = entra_url.read().await;
|
||||
Ok(guard.connect_options())
|
||||
}
|
||||
DatabaseUrl::Static(url) => sqlx::postgres::PgConnectOptions::from_str(url)
|
||||
.map_err(|e| Error::InternalErr(format!("Failed to parse database URL: {}", e))),
|
||||
}
|
||||
@@ -686,31 +671,9 @@ impl DatabaseUrl {
|
||||
match self {
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
DatabaseUrl::IamRds(rds_url) => rds_url.write().await.refresh().await,
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
DatabaseUrl::EntraId(entra_url) => entra_url.write().await.refresh().await,
|
||||
DatabaseUrl::Static(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn needs_refresh(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
DatabaseUrl::IamRds(rds_url) => rds_url.read().await.needs_refresh(),
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
DatabaseUrl::EntraId(entra_url) => entra_url.read().await.needs_refresh(),
|
||||
DatabaseUrl::Static(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Double-checked refresh: read-lock to check, then write-lock to refresh if still needed.
|
||||
pub async fn refresh_if_needed(&self) -> Result<(), Error> {
|
||||
if self.needs_refresh().await {
|
||||
self.refresh().await.map_err(|e| {
|
||||
Error::InternalErr(format!("Failed to refresh database token: {}", e))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
static DATABASE_URL_CACHE: tokio::sync::OnceCell<DatabaseUrl> = tokio::sync::OnceCell::const_new();
|
||||
@@ -738,9 +701,7 @@ pub async fn get_database_url() -> Result<DatabaseUrl, Error> {
|
||||
|
||||
let parsed_url = url::Url::parse(&url)?;
|
||||
|
||||
let password = parsed_url.password().unwrap_or_default();
|
||||
|
||||
if password == "iamrds" {
|
||||
if parsed_url.password().is_some_and(|x| x == "iamrds") {
|
||||
let region = var("AWS_REGION").map_err(|_| {
|
||||
Error::BadConfig(
|
||||
"AWS_REGION env var is required for IAM RDS authentication".to_string(),
|
||||
@@ -770,68 +731,34 @@ pub async fn get_database_url() -> Result<DatabaseUrl, Error> {
|
||||
"IAM RDS authentication is not enabled in OSS mode".to_string(),
|
||||
));
|
||||
}
|
||||
} else if password == "entraid" {
|
||||
let tenant_id = var("AZURE_TENANT_ID").map_err(|_| {
|
||||
Error::BadConfig(
|
||||
"AZURE_TENANT_ID env var is required for Entra ID authentication"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::info!(
|
||||
"entraid mode detected, generating Entra ID URL for tenant: {tenant_id}"
|
||||
);
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
{
|
||||
let client_id = var("AZURE_CLIENT_ID").map_err(|_| {
|
||||
Error::BadConfig(
|
||||
"AZURE_CLIENT_ID env var is required for Entra ID authentication"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
let federated_token_file =
|
||||
var("AZURE_FEDERATED_TOKEN_FILE").map_err(|_| {
|
||||
Error::BadConfig(
|
||||
"AZURE_FEDERATED_TOKEN_FILE env var is required for Entra ID authentication".to_string(),
|
||||
)
|
||||
})?;
|
||||
let authority_host = var("AZURE_AUTHORITY_HOST")
|
||||
.unwrap_or_else(|_| "login.microsoftonline.com".to_string());
|
||||
|
||||
let entra_url = db_entra_ee::generate_database_url(
|
||||
&url,
|
||||
&tenant_id,
|
||||
&client_id,
|
||||
&federated_token_file,
|
||||
&authority_host,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"Failed to generate Entra ID database URL: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
tracing::info!("Entra ID URL generated successfully");
|
||||
Ok::<DatabaseUrl, Error>(DatabaseUrl::EntraId(std::sync::Arc::new(
|
||||
tokio::sync::RwLock::new(entra_url),
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "private")))]
|
||||
{
|
||||
return Err(Error::BadConfig(
|
||||
"Entra ID authentication is not enabled in OSS mode".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
Ok::<DatabaseUrl, Error>(DatabaseUrl::Static(url.to_string()))
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
database_url.refresh_if_needed().await?;
|
||||
// Check if we need to refresh and do so if necessary
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
if let DatabaseUrl::IamRds(ref rds_url_lock) = database_url {
|
||||
// Check if refresh is needed
|
||||
let needs_refresh = {
|
||||
let read_guard = rds_url_lock.read().await;
|
||||
read_guard.needs_refresh()
|
||||
};
|
||||
|
||||
// If refresh is needed, acquire write lock and refresh
|
||||
if needs_refresh {
|
||||
let mut write_guard = rds_url_lock.write().await;
|
||||
// Double-check after acquiring write lock (another task might have refreshed)
|
||||
if write_guard.needs_refresh() {
|
||||
write_guard.refresh().await.map_err(|e| {
|
||||
Error::InternalErr(format!("Failed to refresh IAM token: {}", e))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the URL string
|
||||
Ok(database_url.clone())
|
||||
}
|
||||
|
||||
|
||||
@@ -401,7 +401,7 @@ pub fn build_parameters(columns: &[SimpleColumn], db_type: DbType) -> String {
|
||||
.map(|(i, col)| {
|
||||
let base_type = col.datatype.split('(').next().unwrap_or(&col.datatype);
|
||||
match db_type {
|
||||
DbType::Postgresql => format!("-- ${} {} ({})", i + 1, col.field, base_type),
|
||||
DbType::Postgresql => format!("-- ${} {}", i + 1, col.field),
|
||||
DbType::Mysql => format!("-- :{} ({})", col.field, base_type),
|
||||
DbType::MsSqlServer => {
|
||||
format!("-- @p{} {} ({})", i + 1, col.field, base_type)
|
||||
@@ -765,7 +765,7 @@ pub fn make_select_query(
|
||||
quicksearch
|
||||
));
|
||||
query.push_str(&format!(" ORDER BY {}\n", order_by));
|
||||
query.push_str(" LIMIT $1 OFFSET $2");
|
||||
query.push_str(" LIMIT $1::INT OFFSET $2::INT");
|
||||
Ok(query)
|
||||
}
|
||||
DbType::MsSqlServer => {
|
||||
@@ -1158,11 +1158,13 @@ pub fn make_delete_query(table: &str, columns: &[ColumnDef], db_type: DbType) ->
|
||||
.map(|(i, c)| {
|
||||
let qf = qi(&c.field, db_type);
|
||||
format!(
|
||||
"(${} IS NULL AND {} IS NULL OR {} = ${})",
|
||||
"(${}::text::{} IS NULL AND {} IS NULL OR {} = ${}::text::{})",
|
||||
i + 1,
|
||||
c.datatype,
|
||||
qf,
|
||||
qf,
|
||||
i + 1,
|
||||
c.datatype
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -1278,7 +1280,7 @@ fn format_insert_values(columns: &[ColumnDef], db_type: DbType, start_index: usi
|
||||
.enumerate()
|
||||
.map(|(i, c)| match db_type {
|
||||
DbType::Mysql => format!(":{}", c.field),
|
||||
DbType::Postgresql => format!("${}", start_index + i),
|
||||
DbType::Postgresql => format!("${}::{}", start_index + i, c.datatype),
|
||||
DbType::MsSqlServer => format!("@p{}", start_index + i),
|
||||
DbType::Snowflake => "?".to_string(),
|
||||
DbType::Bigquery => format!("@{}", c.field),
|
||||
@@ -1456,19 +1458,21 @@ pub fn make_update_query(
|
||||
.map(|(i, c)| {
|
||||
let qf = qi(&c.field, db_type);
|
||||
format!(
|
||||
"(${} IS NULL AND {} IS NULL OR {} = ${})",
|
||||
"(${}::text::{} IS NULL AND {} IS NULL OR {} = ${}::text::{})",
|
||||
i + 2,
|
||||
c.datatype,
|
||||
qf,
|
||||
qf,
|
||||
i + 2,
|
||||
c.datatype
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n AND ");
|
||||
|
||||
query.push_str(&format!(
|
||||
"\nUPDATE {} SET {} = $1 \nWHERE {}\tRETURNING 1",
|
||||
qt, qcol, conditions
|
||||
"\nUPDATE {} SET {} = $1::text::{} \nWHERE {}\tRETURNING 1",
|
||||
qt, qcol, column.datatype, conditions
|
||||
));
|
||||
}
|
||||
DbType::Mysql => {
|
||||
@@ -2702,7 +2706,7 @@ mod tests {
|
||||
fn test_build_parameters_postgresql() {
|
||||
let cols = vec![simple_col("limit", "int"), simple_col("offset", "int")];
|
||||
let result = build_parameters(&cols, DbType::Postgresql);
|
||||
assert_eq!(result, "-- $1 limit (int)\n-- $2 offset (int)");
|
||||
assert_eq!(result, "-- $1 limit\n-- $2 offset");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2811,11 +2815,11 @@ mod tests {
|
||||
make_select_query("my_table", &cols, None, DbType::Postgresql, None, None).unwrap();
|
||||
|
||||
assert!(result.starts_with(
|
||||
"-- $1 limit (int)\n-- $2 offset (int)\n-- $3 quicksearch (text)\n-- $4 order_by (text)\n-- $5 is_desc (boolean)\n"
|
||||
"-- $1 limit\n-- $2 offset\n-- $3 quicksearch\n-- $4 order_by\n-- $5 is_desc\n"
|
||||
));
|
||||
assert!(result.contains("SELECT \"id\"::text, \"name\"::text FROM \"my_table\"\n"));
|
||||
assert!(result.contains("($3 = '' OR CONCAT(\"id\", \"name\") ILIKE '%' || $3 || '%')"));
|
||||
assert!(result.contains("LIMIT $1 OFFSET $2"));
|
||||
assert!(result.contains("LIMIT $1::INT OFFSET $2::INT"));
|
||||
assert!(result.contains("$4 = 'id' AND $5 IS false THEN \"id\"::text"));
|
||||
assert!(result.contains("$4 = 'name' AND $5 IS true THEN \"name\"::text END) DESC"));
|
||||
}
|
||||
@@ -3007,7 +3011,7 @@ mod tests {
|
||||
let cols = vec![col("id", "int4"), col("name", "text")];
|
||||
let result = make_count_query(DbType::Postgresql, "my_table", None, &cols).unwrap();
|
||||
|
||||
assert!(result.contains("-- $1 quicksearch (text)"));
|
||||
assert!(result.contains("-- $1 quicksearch"));
|
||||
assert!(result.contains("SELECT COUNT(*) as count FROM \"my_table\""));
|
||||
assert!(result.contains("($1 = '' OR CONCAT(\"id\", \"name\") ILIKE '%' || $1 || '%')"));
|
||||
// Should use WHERE not AND
|
||||
@@ -3144,10 +3148,13 @@ mod tests {
|
||||
let cols = vec![col("id", "int4"), col("name", "text")];
|
||||
let result = make_delete_query("my_table", &cols, DbType::Postgresql);
|
||||
|
||||
assert!(result.contains("-- $1 id (int4)\n-- $2 name (text)"));
|
||||
assert!(result.contains("-- $1 id\n-- $2 name"));
|
||||
assert!(result.contains("DELETE FROM \"my_table\""));
|
||||
assert!(result.contains("($1 IS NULL AND \"id\" IS NULL OR \"id\" = $1)"));
|
||||
assert!(result.contains("($2 IS NULL AND \"name\" IS NULL OR \"name\" = $2)"));
|
||||
assert!(result
|
||||
.contains("($1::text::int4 IS NULL AND \"id\" IS NULL OR \"id\" = $1::text::int4)"));
|
||||
assert!(result.contains(
|
||||
"($2::text::text IS NULL AND \"name\" IS NULL OR \"name\" = $2::text::text)"
|
||||
));
|
||||
assert!(result.contains("RETURNING 1;"));
|
||||
}
|
||||
|
||||
@@ -3214,8 +3221,9 @@ mod tests {
|
||||
let cols = vec![col("id", "int4"), col("name", "text")];
|
||||
let result = make_insert_query("my_table", &cols, DbType::Postgresql).unwrap();
|
||||
|
||||
assert!(result.contains("-- $1 id (int4)\n-- $2 name (text)"));
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"id\", \"name\") VALUES ($1, $2)"));
|
||||
assert!(result.contains("-- $1 id\n-- $2 name"));
|
||||
assert!(result
|
||||
.contains("INSERT INTO \"my_table\" (\"id\", \"name\") VALUES ($1::int4, $2::text)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3271,7 +3279,7 @@ mod tests {
|
||||
make_insert_query("my_table", &[id_col, name_col], DbType::Postgresql).unwrap();
|
||||
|
||||
// id should be skipped from insert columns (has nextval default in pg)
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"name\") VALUES ($1)"));
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"name\") VALUES ($1::text)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3285,7 +3293,9 @@ mod tests {
|
||||
make_insert_query("my_table", &[id_col, name_col], DbType::Postgresql).unwrap();
|
||||
|
||||
// name should be in insert params, id should be in defaults
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"name\", \"id\") VALUES ($1, '42')"));
|
||||
assert!(
|
||||
result.contains("INSERT INTO \"my_table\" (\"name\", \"id\") VALUES ($1::text, '42')")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3298,7 +3308,7 @@ mod tests {
|
||||
let result =
|
||||
make_insert_query("my_table", &[id_col, name_col], DbType::Postgresql).unwrap();
|
||||
|
||||
assert!(result.contains("VALUES ($1, NULL)"));
|
||||
assert!(result.contains("VALUES ($1::text, NULL)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3313,7 +3323,7 @@ mod tests {
|
||||
make_insert_query("my_table", &[id_col, name_col], DbType::Postgresql).unwrap();
|
||||
|
||||
// Column is hidden, not nullable, has db default, no user default -> omit (use db default)
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"name\") VALUES ($1)"));
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"name\") VALUES ($1::text)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3327,7 +3337,7 @@ mod tests {
|
||||
make_insert_query("my_table", &[id_col, name_col], DbType::Postgresql).unwrap();
|
||||
|
||||
// Always identity should be omitted
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"name\") VALUES ($1)"));
|
||||
assert!(result.contains("INSERT INTO \"my_table\" (\"name\") VALUES ($1::text)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3365,12 +3375,13 @@ mod tests {
|
||||
let where_cols = vec![simple_col("id", "int4"), simple_col("email", "text")];
|
||||
let result = make_update_query("my_table", &update_col, &where_cols, DbType::Postgresql);
|
||||
|
||||
assert!(
|
||||
result.contains("-- $1 value_to_update (text)\n-- $2 id (int4)\n-- $3 email (text)")
|
||||
);
|
||||
assert!(result.contains("UPDATE \"my_table\" SET \"name\" = $1"));
|
||||
assert!(result.contains("($2 IS NULL AND \"id\" IS NULL OR \"id\" = $2)"));
|
||||
assert!(result.contains("($3 IS NULL AND \"email\" IS NULL OR \"email\" = $3)"));
|
||||
assert!(result.contains("-- $1 value_to_update\n-- $2 id\n-- $3 email"));
|
||||
assert!(result.contains("UPDATE \"my_table\" SET \"name\" = $1::text::text"));
|
||||
assert!(result
|
||||
.contains("($2::text::int4 IS NULL AND \"id\" IS NULL OR \"id\" = $2::text::int4)"));
|
||||
assert!(result.contains(
|
||||
"($3::text::text IS NULL AND \"email\" IS NULL OR \"email\" = $3::text::text)"
|
||||
));
|
||||
assert!(result.contains("RETURNING 1"));
|
||||
}
|
||||
|
||||
@@ -3452,8 +3463,10 @@ mod tests {
|
||||
let cols = vec![col("a", "int4"), col("b", "text"), col("c", "bool")];
|
||||
let result = make_delete_query("t", &cols, DbType::Postgresql);
|
||||
// Check all three conditions are present and joined
|
||||
assert!(result.contains("AND ($2 IS NULL AND \"b\" IS NULL OR \"b\" = $2)"));
|
||||
assert!(result.contains("AND ($3 IS NULL AND \"c\" IS NULL OR \"c\" = $3)"));
|
||||
assert!(result
|
||||
.contains("AND ($2::text::text IS NULL AND \"b\" IS NULL OR \"b\" = $2::text::text)"));
|
||||
assert!(result
|
||||
.contains("AND ($3::text::bool IS NULL AND \"c\" IS NULL OR \"c\" = $3::text::bool)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3505,7 +3518,7 @@ mod tests {
|
||||
let result = make_insert_query("my_table", &[name_col, col1], DbType::Postgresql).unwrap();
|
||||
|
||||
// Numeric value should not be quoted
|
||||
assert!(result.contains("VALUES ($1, 5)"));
|
||||
assert!(result.contains("VALUES ($1::text, 5)"));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -3527,7 +3540,7 @@ mod tests {
|
||||
assert!(result.is_some());
|
||||
let sql = result.unwrap().unwrap().code;
|
||||
assert!(sql.contains("SELECT \"id\"::text, \"name\"::text FROM \"my_table\""));
|
||||
assert!(sql.contains("LIMIT $1 OFFSET $2"));
|
||||
assert!(sql.contains("LIMIT $1::INT OFFSET $2::INT"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3558,14 +3571,16 @@ mod tests {
|
||||
fn test_expand_insert_marker() {
|
||||
let marker = r#"-- WM_INTERNAL_DB_INSERT {"table":"my_table","columns":[{"field":"id","datatype":"int4"},{"field":"name","datatype":"text"}]}"#;
|
||||
let sql = expand_code(marker, &ScriptLang::Postgresql);
|
||||
assert!(sql.contains("INSERT INTO \"my_table\" (\"id\", \"name\") VALUES ($1, $2)"));
|
||||
assert!(
|
||||
sql.contains("INSERT INTO \"my_table\" (\"id\", \"name\") VALUES ($1::int4, $2::text)")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_update_marker() {
|
||||
let marker = r#"-- WM_INTERNAL_DB_UPDATE {"table":"my_table","column":{"field":"name","datatype":"text"},"columns":[{"field":"id","datatype":"int4"}]}"#;
|
||||
let sql = expand_code(marker, &ScriptLang::Postgresql);
|
||||
assert!(sql.contains("UPDATE \"my_table\" SET \"name\" = $1"));
|
||||
assert!(sql.contains("UPDATE \"my_table\" SET \"name\" = $1::text::text"));
|
||||
assert!(sql.contains("RETURNING 1"));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ pub struct Smtp {
|
||||
pub from: String,
|
||||
pub tls_implicit: Option<bool>,
|
||||
pub disable_tls: Option<bool>,
|
||||
pub clicktracking_off: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq)]
|
||||
@@ -23,7 +22,6 @@ pub struct SmtpConfigOpt {
|
||||
pub smtp_from: Option<String>,
|
||||
pub smtp_tls_implicit: Option<bool>,
|
||||
pub smtp_disable_tls: Option<bool>,
|
||||
pub smtp_clicktracking_off: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn load_smtp_config(db: &DB) -> error::Result<Option<Smtp>> {
|
||||
@@ -48,7 +46,6 @@ pub async fn load_smtp_config(db: &DB) -> error::Result<Option<Smtp>> {
|
||||
from: config
|
||||
.smtp_from
|
||||
.unwrap_or_else(|| "noreply@getwindmill.com".to_string()),
|
||||
clicktracking_off: config.smtp_clicktracking_off,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -75,9 +72,6 @@ pub async fn load_smtp_config(db: &DB) -> error::Result<Option<Smtp>> {
|
||||
.unwrap_or(587),
|
||||
from: std::env::var("SMTP_FROM")
|
||||
.unwrap_or_else(|_| "noreply@getwindmill.com".to_string()),
|
||||
clicktracking_off: std::env::var("SMTP_CLICKTRACKING_OFF")
|
||||
.ok()
|
||||
.and_then(|p| p.parse().ok()),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -100,7 +94,6 @@ impl Default for SmtpConfigOpt {
|
||||
smtp_tls_implicit: None,
|
||||
smtp_username: None,
|
||||
smtp_disable_tls: None,
|
||||
smtp_clicktracking_off: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,8 +157,6 @@ lazy_static::lazy_static! {
|
||||
|
||||
pub static ref NATIVE_MODE: bool = std::env::var("NATIVE_MODE").ok().is_some_and(|x| x == "1" || x == "true");
|
||||
|
||||
pub static ref LIMIT_WINDOWS_TO_1CU: bool = std::env::var("LIMIT_WINDOWS_TO_1CU").ok().is_some_and(|x| x == "1" || x == "true");
|
||||
|
||||
pub static ref CGROUP_V2_PATH_RE: Regex = Regex::new(r#"(?m)^0::(/.*)$"#).unwrap();
|
||||
pub static ref CGROUP_V2_CPU_RE: Regex = Regex::new(r#"(?m)^(\d+) \S+$"#).unwrap();
|
||||
pub static ref CGROUP_V1_INACTIVE_FILE_RE: Regex = Regex::new(r#"(?m)^total_inactive_file (\d+)$"#).unwrap();
|
||||
@@ -1054,9 +1052,6 @@ pub fn get_vcpus() -> Option<i64> {
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn get_vcpus() -> Option<i64> {
|
||||
if *LIMIT_WINDOWS_TO_1CU {
|
||||
return Some(100000); // 1 vCPU
|
||||
}
|
||||
let mut sys = System::new();
|
||||
sys.refresh_cpu_all();
|
||||
(sys.cpus().len() * 100000).try_into().ok()
|
||||
@@ -1106,9 +1101,6 @@ pub fn get_memory() -> Option<i64> {
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn get_memory() -> Option<i64> {
|
||||
if *LIMIT_WINDOWS_TO_1CU {
|
||||
return Some(2 * 1024 * 1024 * 1024); // 2 GB
|
||||
}
|
||||
let mut sys = System::new();
|
||||
sys.refresh_memory();
|
||||
Some(sys.total_memory() as i64)
|
||||
|
||||
@@ -42,39 +42,13 @@ pub fn extract_resource_types_from_schema(schema: &SchemaType) -> HashSet<String
|
||||
|
||||
/// Transform a JSON schema for maximum MCP client compatibility.
|
||||
///
|
||||
/// Ensures schemas conform to JSON Schema draft 2020-12 by:
|
||||
/// - Converting `integer` type to `number` (some clients don't support integer)
|
||||
/// - Removing invalid non-array `enum` values
|
||||
/// - Stripping non-standard keywords (`originalType`, `format` with `resource-*` prefix)
|
||||
/// - Fixing contradictory schemas (`type: "string"` with `properties` → `type: "object"`)
|
||||
/// - Removing `default: null` when the type doesn't include `null`
|
||||
/// - Adding `type: "object"` to empty schemas that have no type
|
||||
/// Some MCP clients (e.g., n8n) have limited JSON Schema support:
|
||||
/// - `integer` type is not supported (convert to `number`)
|
||||
/// - invalid non-array `enum` values are removed
|
||||
pub fn make_schema_compatible(schema: &mut Value) {
|
||||
let Value::Object(obj) = schema else { return };
|
||||
|
||||
// 1. Strip non-standard keywords that aren't part of JSON Schema
|
||||
obj.remove("originalType");
|
||||
|
||||
// 2. Strip non-standard format values (resource-* is Windmill-internal)
|
||||
if obj
|
||||
.get("format")
|
||||
.and_then(|v| v.as_str())
|
||||
.is_some_and(|s| s.starts_with("resource-"))
|
||||
{
|
||||
obj.remove("format");
|
||||
}
|
||||
|
||||
// 3. Fix contradictory type: if `properties` is present, type must be "object"
|
||||
if obj.contains_key("properties") {
|
||||
match obj.get("type").and_then(|v| v.as_str()) {
|
||||
Some("object") => {}
|
||||
_ => {
|
||||
obj.insert("type".to_string(), Value::String("object".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Convert integer to number
|
||||
// 1. Convert integer to number
|
||||
if let Some(type_val) = obj.get_mut("type") {
|
||||
match type_val {
|
||||
Value::String(s) if s == "integer" => *s = "number".to_string(),
|
||||
@@ -91,28 +65,11 @@ pub fn make_schema_compatible(schema: &mut Value) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Remove `default: null` when type doesn't include "null"
|
||||
if obj.get("default").is_some_and(|v| v.is_null()) {
|
||||
let type_includes_null = match obj.get("type") {
|
||||
Some(Value::String(s)) => s == "null",
|
||||
Some(Value::Array(arr)) => arr.iter().any(|v| v.as_str() == Some("null")),
|
||||
_ => false,
|
||||
};
|
||||
if !type_includes_null {
|
||||
obj.remove("default");
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Invalid enum values like `enum: null` are not valid draft 2020-12.
|
||||
// 2. Invalid enum values like `enum: null` are not valid draft 2020-12.
|
||||
if obj.get("enum").is_some_and(|enum_val| !enum_val.is_array()) {
|
||||
obj.remove("enum");
|
||||
}
|
||||
|
||||
// 7. Ensure schemas with no type but with properties get type: "object"
|
||||
if !obj.contains_key("type") && !obj.is_empty() {
|
||||
obj.insert("type".to_string(), Value::String("object".to_string()));
|
||||
}
|
||||
|
||||
// Recursively process nested schemas
|
||||
if let Some(Value::Object(props)) = obj.get_mut("properties") {
|
||||
for value in props.values_mut() {
|
||||
@@ -217,155 +174,4 @@ mod tests {
|
||||
json!(["open", "closed"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_original_type() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"originalType": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"originalType": "bytes"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert!(schema["properties"]["name"].get("originalType").is_none());
|
||||
assert!(schema["properties"]["data"].get("originalType").is_none());
|
||||
assert_eq!(schema["properties"]["name"]["type"], json!("string"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_resource_format() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"db": {
|
||||
"type": "string",
|
||||
"format": "resource-postgresql"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert!(schema["properties"]["db"].get("format").is_none());
|
||||
assert_eq!(schema["properties"]["db"]["type"], json!("string"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_standard_format() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert_eq!(schema["properties"]["email"]["format"], json!("email"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixes_string_type_with_properties() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"type": "string",
|
||||
"format": "resource-record",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert_eq!(schema["properties"]["config"]["type"], json!("object"));
|
||||
assert!(schema["properties"]["config"].get("format").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removes_null_default_on_string_type() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": null
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert!(schema["properties"]["name"].get("default").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_null_default_when_type_includes_null() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": ["string", "null"],
|
||||
"default": null
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert_eq!(schema["properties"]["name"]["default"], json!(null));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_non_null_default() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "hello"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert_eq!(schema["properties"]["name"]["default"], json!("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_type_object_to_empty_schema() {
|
||||
let mut schema = json!({});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
// Empty schema stays empty (no keys = truly empty)
|
||||
assert_eq!(schema, json!({}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_type_to_schema_with_properties_but_no_type() {
|
||||
let mut schema = json!({
|
||||
"properties": {
|
||||
"value": {}
|
||||
}
|
||||
});
|
||||
|
||||
make_schema_compatible(&mut schema);
|
||||
|
||||
assert_eq!(schema["type"], json!("object"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +146,6 @@ hyper-tls = { workspace = true, optional = true }
|
||||
hyper-util = { workspace = true, optional = true }
|
||||
rcgen = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.61", features = ["Win32_System_JobObjects", "Win32_System_Threading"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
x509-parser.workspace = true
|
||||
|
||||
@@ -6,15 +6,8 @@ try {
|
||||
external: ["*"],
|
||||
});
|
||||
} catch (err) {
|
||||
const msgs = (err?.errors ?? []).map((e) => e?.message ?? String(e));
|
||||
msgs.push(err?.message ?? String(err));
|
||||
const full = msgs.join(" ");
|
||||
if (full.includes("Unexpected end of file") || full.includes("Unterminated")) {
|
||||
console.log("Failed to build bundle: syntax error in the script (e.g. unclosed bracket, string, or comment)");
|
||||
} else {
|
||||
console.log("Failed to build bundle");
|
||||
}
|
||||
console.log(err);
|
||||
console.log("Failed to build bundle");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -702,120 +702,6 @@ lazy_static! {
|
||||
static ref DISABLE_PROCESS_GROUP: bool = std::env::var("DISABLE_PROCESS_GROUP").is_ok();
|
||||
}
|
||||
|
||||
/// 2 GB memory limit in bytes for LIMIT_WINDOWS_TO_1CU
|
||||
#[cfg(windows)]
|
||||
const MEMORY_LIMIT_1CU: usize = 2 * 1024 * 1024 * 1024;
|
||||
|
||||
/// Wrapper that holds a Windows Job Object handle alongside the child process.
|
||||
/// The job object enforces memory limits and is closed when the child is dropped.
|
||||
#[cfg(windows)]
|
||||
struct MemoryLimitedChild {
|
||||
inner: Box<dyn TokioChildWrapper>,
|
||||
_job_handle: Win32JobHandle,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::fmt::Debug for MemoryLimitedChild {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MemoryLimitedChild").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// RAII wrapper for a raw Win32 HANDLE that closes it on drop.
|
||||
#[cfg(windows)]
|
||||
struct Win32JobHandle(windows::Win32::Foundation::HANDLE);
|
||||
|
||||
// SAFETY: Win32 HANDLEs are plain pointer-sized values with no thread affinity;
|
||||
// the kernel ref-counts the underlying object, so sending/sharing the handle is safe.
|
||||
#[cfg(windows)]
|
||||
unsafe impl Send for Win32JobHandle {}
|
||||
#[cfg(windows)]
|
||||
unsafe impl Sync for Win32JobHandle {}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Drop for Win32JobHandle {
|
||||
fn drop(&mut self) {
|
||||
let _ = unsafe { windows::Win32::Foundation::CloseHandle(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl process_wrap::tokio::TokioChildWrapper for MemoryLimitedChild {
|
||||
fn inner(&self) -> &tokio::process::Child {
|
||||
self.inner.inner()
|
||||
}
|
||||
fn inner_mut(&mut self) -> &mut tokio::process::Child {
|
||||
self.inner.inner_mut()
|
||||
}
|
||||
fn into_inner(self: Box<Self>) -> tokio::process::Child {
|
||||
self.inner.into_inner()
|
||||
}
|
||||
fn start_kill(&mut self) -> std::io::Result<()> {
|
||||
self.inner.start_kill()
|
||||
}
|
||||
fn wait(
|
||||
&mut self,
|
||||
) -> Box<dyn std::future::Future<Output = std::io::Result<std::process::ExitStatus>> + Send + '_>
|
||||
{
|
||||
self.inner.wait()
|
||||
}
|
||||
fn try_wait(&mut self) -> std::io::Result<Option<std::process::ExitStatus>> {
|
||||
self.inner.try_wait()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Windows Job Object with a memory limit and assign the process to it.
|
||||
#[cfg(windows)]
|
||||
fn apply_job_memory_limit(pid: u32, memory_limit: usize) -> Result<Win32JobHandle, std::io::Error> {
|
||||
use windows::Win32::System::JobObjects::*;
|
||||
use windows::Win32::System::Threading::{OpenProcess, PROCESS_SET_QUOTA, PROCESS_TERMINATE};
|
||||
|
||||
unsafe {
|
||||
let job = CreateJobObjectW(None, None).map_err(|e| {
|
||||
std::io::Error::new(std::io::ErrorKind::Other, format!("CreateJobObjectW: {e}"))
|
||||
})?;
|
||||
|
||||
let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
|
||||
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY;
|
||||
info.JobMemoryLimit = memory_limit;
|
||||
|
||||
SetInformationJobObject(
|
||||
job,
|
||||
JobObjectExtendedLimitInformation,
|
||||
&info as *const _ as _,
|
||||
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let _ = windows::Win32::Foundation::CloseHandle(job);
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("SetInformationJobObject: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let process_handle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, pid)
|
||||
.map_err(|e| {
|
||||
let _ = windows::Win32::Foundation::CloseHandle(job);
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("OpenProcess({pid}): {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let assign_result = AssignProcessToJobObject(job, process_handle);
|
||||
let _ = windows::Win32::Foundation::CloseHandle(process_handle);
|
||||
assign_result.map_err(|e| {
|
||||
let _ = windows::Win32::Foundation::CloseHandle(job);
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("AssignProcessToJobObject: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Win32JobHandle(job))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_command_with_isolation(program: &str, args: &[&str]) -> Command {
|
||||
use tokio::process::Command;
|
||||
|
||||
@@ -881,31 +767,9 @@ pub async fn start_child_process(
|
||||
}
|
||||
}
|
||||
|
||||
let child: Box<dyn TokioChildWrapper> = cmd
|
||||
return cmd
|
||||
.spawn()
|
||||
.map_err(|err| tentatively_improve_error(err.into(), executable))?;
|
||||
|
||||
#[cfg(windows)]
|
||||
if *windmill_common::worker::LIMIT_WINDOWS_TO_1CU {
|
||||
if let Some(pid) = child.inner().id() {
|
||||
match apply_job_memory_limit(pid, MEMORY_LIMIT_1CU) {
|
||||
Ok(job_handle) => {
|
||||
tracing::info!(
|
||||
"Applied 2GB memory limit (LIMIT_WINDOWS_TO_1CU) to child process {pid}"
|
||||
);
|
||||
return Ok(Box::new(MemoryLimitedChild {
|
||||
inner: child,
|
||||
_job_handle: job_handle,
|
||||
}));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to apply memory limit to child process {pid}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(child)
|
||||
.map_err(|err| tentatively_improve_error(err.into(), executable));
|
||||
}
|
||||
|
||||
pub async fn resolve_job_timeout(
|
||||
|
||||
@@ -8,7 +8,7 @@ use anyhow::Context;
|
||||
use base64::{engine, Engine as _};
|
||||
use chrono::Utc;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
|
||||
use itertools::Itertools;
|
||||
use rust_decimal::{prelude::FromPrimitive, Decimal};
|
||||
use serde_json::value::RawValue;
|
||||
@@ -134,45 +134,45 @@ fn do_postgresql_inner<'a>(
|
||||
let result_f = async move {
|
||||
let mut res: Vec<Box<serde_json::value::RawValue>> = vec![];
|
||||
|
||||
// Use query_typed_raw (unnamed prepared statement) when all param types are
|
||||
// resolved. This avoids named prepared statements ("s0", "s1", ...) which break
|
||||
// with transaction-mode connection poolers (e.g. PgBouncer/Supabase) since the
|
||||
// prepare and query can land on different backend connections.
|
||||
// Fall back to prepare + query_raw for custom/unsupported types.
|
||||
let rows = if typed_schema {
|
||||
let typed_params = query_params
|
||||
.iter()
|
||||
.zip(param_types.iter())
|
||||
.map(|(p, t)| (&**p as &(dyn ToSql + Sync), t.clone()));
|
||||
let query_params = query_params
|
||||
.iter()
|
||||
.map(|p| &**p as &(dyn ToSql + Sync))
|
||||
.collect_vec();
|
||||
|
||||
let statement = if typed_schema {
|
||||
client
|
||||
.query_typed_raw(&query, typed_params)
|
||||
.prepare_typed(&query, ¶m_types)
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
} else {
|
||||
let query_params = query_params
|
||||
.iter()
|
||||
.map(|p| &**p as &(dyn ToSql + Sync))
|
||||
.collect_vec();
|
||||
let statement = client.prepare(&query).await.map_err(to_anyhow)?;
|
||||
client
|
||||
.query_raw(&statement, query_params)
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
client.prepare(&query).await.map_err(to_anyhow)?
|
||||
};
|
||||
|
||||
if skip_collect {
|
||||
futures::pin_mut!(rows);
|
||||
while rows.try_next().await.map_err(to_anyhow)?.is_some() {}
|
||||
client
|
||||
.execute_raw(&statement, query_params)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
} else if let Some(ref s3) = s3 {
|
||||
let rows_stream = rows.map_err(to_anyhow).map(|row_result| {
|
||||
row_result.and_then(|row| postgres_row_to_json_value(row).map_err(to_anyhow))
|
||||
});
|
||||
let rows_stream = client
|
||||
.query_raw(&statement, query_params)
|
||||
.map_err(to_anyhow)
|
||||
.await?
|
||||
.map_err(to_anyhow)
|
||||
.map(|row_result| {
|
||||
row_result.and_then(|row| postgres_row_to_json_value(row).map_err(to_anyhow))
|
||||
});
|
||||
|
||||
let stream = convert_json_line_stream(rows_stream.boxed(), s3.format).await?;
|
||||
s3.upload(stream.boxed()).await?;
|
||||
|
||||
return Ok(vec![to_raw_value(&s3.to_return_s3_obj())]);
|
||||
} else {
|
||||
let rows = client
|
||||
.query_raw(&statement, query_params)
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
|
||||
let rows = if first_row_only {
|
||||
rows.take(1).boxed()
|
||||
} else {
|
||||
|
||||
@@ -429,7 +429,7 @@ pub async fn handle_flow_dependency_job(
|
||||
// `JobKind::FlowDependencies` job store either:
|
||||
// - A saved flow version `id` in the `script_hash` column.
|
||||
// - Preview raw flow in the `queue` or `job` table.
|
||||
let (mut flow, mut extras) = match job.runnable_id {
|
||||
let (mut flow, extras) = match job.runnable_id {
|
||||
Some(ScriptHash(id)) => {
|
||||
let flow = cache::flow::fetch_version(db, id).await?;
|
||||
(flow.value().clone(), flow.extras())
|
||||
@@ -440,51 +440,6 @@ pub async fn handle_flow_dependency_job(
|
||||
},
|
||||
};
|
||||
|
||||
// When triggered by a relative import (e.g. a dependent script was updated),
|
||||
// the version captured at job creation time may be stale if the flow was
|
||||
// updated between job creation and execution. Re-query the latest version
|
||||
// and read the current flow value from the flow table to avoid overwriting
|
||||
// a newer flow definition with a stale one.
|
||||
let version = if triggered_by_relative_import && !skip_flow_update {
|
||||
let latest_version = sqlx::query_scalar!(
|
||||
"SELECT id FROM flow_version WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
|
||||
job_path,
|
||||
job.workspace_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
if let Some(latest_version) = latest_version {
|
||||
if version != Some(latest_version) {
|
||||
tracing::info!(
|
||||
"Flow version changed since dependency job was queued ({:?} -> {}), using latest",
|
||||
version,
|
||||
latest_version
|
||||
);
|
||||
}
|
||||
// Read the current flow value from the flow table (not version cache).
|
||||
// This ensures we have the latest committed state, including any locks
|
||||
// computed by a concurrent FlowDependencies job from a direct flow update.
|
||||
let raw_flow_value = sqlx::query_scalar!(
|
||||
"SELECT value AS \"value!: Json<Box<RawValue>>\" FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
job_path,
|
||||
job.workspace_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
|
||||
let flow_data = cache::FlowData::from_raw(raw_flow_value.0)?;
|
||||
flow = flow_data.value().clone();
|
||||
extras = flow_data.extras();
|
||||
|
||||
Some(latest_version)
|
||||
} else {
|
||||
version
|
||||
}
|
||||
} else {
|
||||
version
|
||||
};
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let mut dependency_map = ScopedDependencyMap::fetch_maybe_rearranged(
|
||||
@@ -620,33 +575,6 @@ pub async fn handle_flow_dependency_job(
|
||||
|
||||
tx = dependency_map.dissolve(tx).await;
|
||||
|
||||
// When triggered by a relative import, re-check that our version is still
|
||||
// the latest before writing. Between reading the flow value and now (module
|
||||
// locking can take significant time), another job may have created a newer
|
||||
// version. If so, skip the update — the newer version's dep job will handle it.
|
||||
if triggered_by_relative_import {
|
||||
let current_latest = sqlx::query_scalar!(
|
||||
"SELECT id FROM flow_version WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
|
||||
job_path,
|
||||
job.workspace_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if current_latest != Some(version) {
|
||||
tracing::info!(
|
||||
"Flow version changed during dependency locking ({} -> {:?}), skipping update to avoid overwriting newer version",
|
||||
version,
|
||||
current_latest
|
||||
);
|
||||
tx.commit().await?;
|
||||
return Ok(to_raw_value_owned(json!({
|
||||
"status": "Skipped: newer flow version exists",
|
||||
"modified_ids": modified_ids,
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE flow SET value = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
&new_flow_value as &Json<Box<RawValue>>,
|
||||
@@ -699,9 +627,7 @@ pub async fn handle_flow_dependency_job(
|
||||
// Making new version viewable as the current one.
|
||||
// This will also trigger `flow_versions_append_trigger` (check _flow_versions_update_notify.up.sql)
|
||||
// which will invalidate cache for the latest flow versions for all workers.
|
||||
// Only append if this version isn't already the last element in the array.
|
||||
// This prevents duplicates when update_flow already appended this version.
|
||||
sqlx::query!("UPDATE flow SET versions = array_append(versions, $1) WHERE path = $2 AND workspace_id = $3 AND (versions[array_upper(versions, 1)] IS DISTINCT FROM $1)",
|
||||
sqlx::query!("UPDATE flow SET versions = array_append(versions, $1) WHERE path = $2 AND workspace_id = $3",
|
||||
version,
|
||||
&job_path,
|
||||
&job.workspace_id,
|
||||
|
||||
@@ -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.673.0";
|
||||
export const VERSION = "v1.672.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -15,7 +15,7 @@ import { buildFolderPath, getMetadataFileName, loadNonDottedPathsSetting } from
|
||||
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
import { resolve, track_job, pollForJobResult } from "../script/script.ts";
|
||||
import { resolve, track_job } from "../script/script.ts";
|
||||
import { defaultFlowDefinition } from "../../../bootstrap/flow_bootstrap.ts";
|
||||
import { SyncOptions, mergeConfigWithConfigFile } from "../../core/conf.ts";
|
||||
import { FSFSElement, elementsToMap, ignoreF } from "../sync/sync.ts";
|
||||
@@ -574,27 +574,32 @@ async function preview(
|
||||
|
||||
log.debug(`Flow value: ${JSON.stringify(localFlow.value, null, 2)}`);
|
||||
|
||||
// Run the flow preview — start the job, then poll for completion
|
||||
const jobId = await wmill.runFlowPreview({
|
||||
workspace: workspace.workspaceId,
|
||||
requestBody: {
|
||||
value: localFlow.value,
|
||||
path: flowPath.substring(0, flowPath.indexOf(".flow")).replaceAll(SEP, "/"),
|
||||
args: input,
|
||||
},
|
||||
});
|
||||
|
||||
const { result, success } = await pollForJobResult(workspace.workspaceId, jobId);
|
||||
|
||||
if (!success) {
|
||||
if (opts.silent) {
|
||||
console.log(JSON.stringify(result));
|
||||
} else {
|
||||
log.info(colors.yellow.bold("Flow failed, error handler result:"));
|
||||
log.info(JSON.stringify(result, null, 2));
|
||||
// Run the flow preview
|
||||
let result;
|
||||
try {
|
||||
result = await wmill.runFlowPreviewAndWaitResult({
|
||||
workspace: workspace.workspaceId,
|
||||
requestBody: {
|
||||
value: localFlow.value,
|
||||
path: flowPath.substring(0, flowPath.indexOf(".flow")).replaceAll(SEP, "/"),
|
||||
args: input,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (e.body) {
|
||||
// If a failure_module ran, the body contains its result — not an error
|
||||
if (e.body.result !== undefined) {
|
||||
if (opts.silent) {
|
||||
console.log(JSON.stringify(e.body.result));
|
||||
} else {
|
||||
log.info(colors.yellow.bold("Flow failed, error handler result:"));
|
||||
log.info(JSON.stringify(e.body.result, null, 2));
|
||||
}
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (opts.silent) {
|
||||
|
||||
@@ -333,9 +333,8 @@ async function generateMetadata(
|
||||
|
||||
log.info("");
|
||||
|
||||
const isInteractive = process.stdin.isTTY ?? false;
|
||||
if (
|
||||
!opts.yes && isInteractive &&
|
||||
!opts.yes &&
|
||||
!(await Confirm.prompt({
|
||||
message: "Update metadata?",
|
||||
default: true,
|
||||
|
||||
@@ -1118,27 +1118,6 @@ export async function track_job(workspace: string, id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
|
||||
export async function pollForJobResult(
|
||||
workspace: string,
|
||||
jobId: string,
|
||||
): Promise<{ result: unknown; success: boolean }> {
|
||||
while (true) {
|
||||
const maybeResult = await wmill.getCompletedJobResultMaybe({
|
||||
workspace,
|
||||
id: jobId,
|
||||
getStarted: false,
|
||||
});
|
||||
|
||||
if (maybeResult.completed) {
|
||||
return { result: maybeResult.result, success: maybeResult.success ?? false };
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
|
||||
async function show(opts: GlobalOptions, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
@@ -1553,8 +1532,8 @@ async function preview(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For regular scripts, start the preview job then poll for completion
|
||||
const jobId = await wmill.runScriptPreview({
|
||||
// For regular scripts, use the standard preview API
|
||||
const result = await wmill.runScriptPreviewAndWaitResult({
|
||||
workspace: workspace.workspaceId,
|
||||
requestBody: {
|
||||
content,
|
||||
@@ -1565,21 +1544,8 @@ async function preview(
|
||||
},
|
||||
});
|
||||
|
||||
const { result, success } = await pollForJobResult(workspace.workspaceId, jobId);
|
||||
|
||||
if (!success) {
|
||||
if (opts.silent) {
|
||||
console.log(JSON.stringify(result));
|
||||
} else {
|
||||
log.info(colors.red.bold("Preview failed"));
|
||||
log.info(JSON.stringify(result, null, 2));
|
||||
}
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.silent) {
|
||||
console.log(JSON.stringify(result));
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
log.info(colors.bold.underline.green("Preview completed"));
|
||||
log.info(JSON.stringify(result, null, 2));
|
||||
|
||||
@@ -78,7 +78,7 @@ export {
|
||||
token,
|
||||
};
|
||||
|
||||
export const VERSION = "1.673.0";
|
||||
export const VERSION = "1.672.0";
|
||||
|
||||
// Re-exported from constants.ts to maintain backwards compatibility
|
||||
export { WM_FORK_PREFIX } from "./core/constants.ts";
|
||||
|
||||
@@ -1053,12 +1053,12 @@ export async function parseMetadataFile(
|
||||
}
|
||||
}
|
||||
// no metadata file at all. Create it
|
||||
metadataFilePath = scriptPath + ".script.yaml";
|
||||
log.info(
|
||||
(await blueColor())(
|
||||
`Creating script metadata file for ${metadataFilePath}`
|
||||
)
|
||||
);
|
||||
metadataFilePath = scriptPath + ".script.yaml";
|
||||
let scriptInitialMetadata = defaultScriptMetadata();
|
||||
const lockPath = scriptPath + ".script.lock";
|
||||
scriptInitialMetadata.lock = "!inline " + lockPath;
|
||||
|
||||
@@ -385,145 +385,7 @@ lock: ""
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// Test 4: Hash consistency - generate-metadata (tree) then deprecated check (legacy)
|
||||
// =============================================================================
|
||||
|
||||
test("Workspace deps: hash consistency between tree-based and legacy staleness checks", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
const testWorkspace = {
|
||||
remote: backend.baseUrl,
|
||||
workspaceId: backend.workspace,
|
||||
name: "workspace_deps_hash_consistency_test",
|
||||
token: backend.token
|
||||
};
|
||||
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
|
||||
|
||||
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
excludes: []`, "utf-8");
|
||||
|
||||
// Setup workspace deps
|
||||
await mkdir(`${tempDir}/dependencies`, { recursive: true });
|
||||
const bunDep = `{"dependencies": {"lodash": "4.17.21"}}`;
|
||||
await writeFile(`${tempDir}/dependencies/package.json`, bunDep, "utf-8");
|
||||
|
||||
// Setup script
|
||||
await mkdir(`${tempDir}/f/test`, { recursive: true });
|
||||
const scriptContent = `export async function main() {
|
||||
return "uses default deps";
|
||||
}
|
||||
`;
|
||||
const scriptMetadata = `summary: "test"
|
||||
schema:
|
||||
type: object
|
||||
properties: {}
|
||||
lock: ""
|
||||
`;
|
||||
await writeFile(`${tempDir}/f/test/my_script.ts`, scriptContent, "utf-8");
|
||||
await writeFile(`${tempDir}/f/test/my_script.script.yaml`, scriptMetadata, "utf-8");
|
||||
|
||||
// Step 1: Run the NEW generate-metadata command (tree-based, stores hash with {} for deps)
|
||||
const genResult = await backend.runCLICommand(
|
||||
["generate-metadata", "--yes", "--schema-only"],
|
||||
tempDir,
|
||||
"workspace_deps_hash_consistency_test"
|
||||
);
|
||||
expect(genResult.code).toEqual(0);
|
||||
expect(genResult.stdout).toContain("Done. Updated");
|
||||
|
||||
// Step 2: Run the DEPRECATED script generate-metadata --dry-run (legacy, checks hash with deps)
|
||||
// This simulates what sync push does internally — the legacy check path.
|
||||
// If hashes are inconsistent, this would falsely report the script as stale.
|
||||
const legacyResult = await backend.runCLICommand(
|
||||
["script", "generate-metadata", "-i", "f/test/my_script*", "--yes", "--dry-run"],
|
||||
tempDir,
|
||||
"workspace_deps_hash_consistency_test"
|
||||
);
|
||||
expect(legacyResult.code).toEqual(0);
|
||||
// The legacy check should NOT find it stale — generate-metadata persisted
|
||||
// the dep hashes, so getRawWorkspaceDependencies(true) returns {} for the
|
||||
// legacy path, making the hash formula equivalent.
|
||||
expect(legacyResult.stdout).toContain("No metadata to update");
|
||||
});
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// Test 5: Dep change after generate-metadata — legacy check detects staleness
|
||||
// =============================================================================
|
||||
|
||||
test("Workspace deps: dep change after generate-metadata is detected by legacy check", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
const testWorkspace = {
|
||||
remote: backend.baseUrl,
|
||||
workspaceId: backend.workspace,
|
||||
name: "workspace_deps_dep_change_test",
|
||||
token: backend.token
|
||||
};
|
||||
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
|
||||
|
||||
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
excludes: []`, "utf-8");
|
||||
|
||||
// Setup workspace deps
|
||||
await mkdir(`${tempDir}/dependencies`, { recursive: true });
|
||||
const bunDep = `{"dependencies": {"lodash": "4.17.21"}}`;
|
||||
await writeFile(`${tempDir}/dependencies/package.json`, bunDep, "utf-8");
|
||||
|
||||
// Setup script (no annotation = uses default dep)
|
||||
await mkdir(`${tempDir}/f/test`, { recursive: true });
|
||||
const scriptContent = `export async function main() {
|
||||
return "uses default deps";
|
||||
}
|
||||
`;
|
||||
const scriptMetadata = `summary: "test"
|
||||
schema:
|
||||
type: object
|
||||
properties: {}
|
||||
lock: ""
|
||||
`;
|
||||
await writeFile(`${tempDir}/f/test/my_script.ts`, scriptContent, "utf-8");
|
||||
await writeFile(`${tempDir}/f/test/my_script.script.yaml`, scriptMetadata, "utf-8");
|
||||
|
||||
// Step 1: Run generate-metadata to establish baseline hashes
|
||||
const genResult = await backend.runCLICommand(
|
||||
["generate-metadata", "--yes", "--schema-only"],
|
||||
tempDir,
|
||||
"workspace_deps_dep_change_test"
|
||||
);
|
||||
expect(genResult.code).toEqual(0);
|
||||
|
||||
// Step 2: Verify baseline is up-to-date via legacy check
|
||||
const checkResult = await backend.runCLICommand(
|
||||
["script", "generate-metadata", "-i", "f/test/my_script*", "--yes", "--dry-run"],
|
||||
tempDir,
|
||||
"workspace_deps_dep_change_test"
|
||||
);
|
||||
expect(checkResult.code).toEqual(0);
|
||||
expect(checkResult.stdout).toContain("No metadata to update");
|
||||
|
||||
// Step 3: Change the workspace dep
|
||||
const newBunDep = `{"dependencies": {"lodash": "4.17.22"}}`;
|
||||
await writeFile(`${tempDir}/dependencies/package.json`, newBunDep, "utf-8");
|
||||
|
||||
// Step 4: Legacy check should detect the dep change as stale
|
||||
// This works because getRawWorkspaceDependencies(true) returns the changed dep
|
||||
// (its content hash no longer matches the lockfile), and the legacy path
|
||||
// includes deps in the hash formula.
|
||||
const afterDepChangeResult = await backend.runCLICommand(
|
||||
["script", "generate-metadata", "-i", "f/test/my_script*", "--yes", "--dry-run"],
|
||||
tempDir,
|
||||
"workspace_deps_dep_change_test"
|
||||
);
|
||||
expect(afterDepChangeResult.code).toEqual(0);
|
||||
expect(afterDepChangeResult.stdout).toContain("my_script");
|
||||
});
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// Test 6: Apps - Create app via API and test filterWorkspaceDependenciesForApp
|
||||
// Test 4: Apps - Create app via API and test filterWorkspaceDependenciesForApp
|
||||
// =============================================================================
|
||||
|
||||
test("Workspace deps: Apps - filterWorkspaceDependenciesForApp with real app via API", async () => {
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.673.0",
|
||||
"version": "1.672.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.673.0",
|
||||
"version": "1.672.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.673.0",
|
||||
"version": "1.672.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -132,10 +132,14 @@
|
||||
}
|
||||
})
|
||||
for (const dep of sortedSet) {
|
||||
allAlreadyExists[computeStatusPath(dep.kind, dep.path)] = await checkAlreadyExists(
|
||||
dep.kind,
|
||||
dep.path
|
||||
)
|
||||
try {
|
||||
allAlreadyExists[computeStatusPath(dep.kind, dep.path)] = await checkAlreadyExists(
|
||||
dep.kind,
|
||||
dep.path
|
||||
)
|
||||
} catch {
|
||||
allAlreadyExists[computeStatusPath(dep.kind, dep.path)] = false
|
||||
}
|
||||
}
|
||||
dependencies = sortedSet.map((x) => ({
|
||||
...x,
|
||||
|
||||
@@ -38,10 +38,7 @@
|
||||
export async function run(overrideScheduledForStr?: string | undefined | null) {
|
||||
let processedArgs: Record<string, any>
|
||||
try {
|
||||
processedArgs = await processSecretArgs(
|
||||
enforceDisabledDefaults(args ?? {}, true),
|
||||
runnable?.schema
|
||||
)
|
||||
processedArgs = await processSecretArgs(args ?? {}, runnable?.schema)
|
||||
} catch (e) {
|
||||
sendUserToast('Failed to process sensitive args: ' + e, true)
|
||||
return
|
||||
@@ -135,30 +132,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function enforceDisabledDefaults(
|
||||
args: Record<string, any>,
|
||||
notify: boolean = false
|
||||
): Record<string, any> {
|
||||
const schema = runnable?.schema
|
||||
if (!schema?.properties) return args
|
||||
const result = { ...args }
|
||||
const resetKeys: string[] = []
|
||||
for (const [key, prop] of Object.entries(schema.properties) as [string, any][]) {
|
||||
if (prop?.disabled && 'default' in prop) {
|
||||
if (notify && result[key] !== prop.default) {
|
||||
resetKeys.push(key)
|
||||
}
|
||||
result[key] = prop.default
|
||||
}
|
||||
}
|
||||
if (resetKeys.length > 0) {
|
||||
sendUserToast(
|
||||
`Disabled field${resetKeys.length > 1 ? 's' : ''} ${resetKeys.map((k) => `'${k}'`).join(', ')} reset to default value${resetKeys.length > 1 ? 's' : ''}`
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function setCode(code: string) {
|
||||
jsonEditor?.setCode(code)
|
||||
}
|
||||
@@ -276,7 +249,7 @@
|
||||
bind:this={jsonEditor}
|
||||
on:select={(e) => {
|
||||
if (e.detail) {
|
||||
args = enforceDisabledDefaults(e.detail)
|
||||
args = e.detail
|
||||
}
|
||||
}}
|
||||
updateOnBlur={false}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
RotateCcw,
|
||||
Settings,
|
||||
Trash,
|
||||
Power,
|
||||
X,
|
||||
ExternalLink,
|
||||
FileCode
|
||||
@@ -15,13 +14,7 @@
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import {
|
||||
ConfigService,
|
||||
SettingService,
|
||||
WorkspaceService,
|
||||
type WorkerPing,
|
||||
type Workspace
|
||||
} from '$lib/gen'
|
||||
import { ConfigService, WorkspaceService, type WorkerPing, type Workspace } from '$lib/gen'
|
||||
import ConfirmationModal from './common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
@@ -271,7 +264,6 @@
|
||||
})
|
||||
let openDelete = $state(false)
|
||||
let openClean = $state(false)
|
||||
let openRestart = $state(false)
|
||||
|
||||
// Compute hashed tags for display (actual tags used by the worker)
|
||||
let hashedDedicatedTags: Map<string, string> = $state(new Map())
|
||||
@@ -373,32 +365,6 @@
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
open={openRestart}
|
||||
title="Restart workers"
|
||||
confirmationText="Restart"
|
||||
on:canceled={() => {
|
||||
openRestart = false
|
||||
}}
|
||||
on:confirmed={async () => {
|
||||
try {
|
||||
await SettingService.restartWorkerGroup({ workerGroup: name })
|
||||
sendUserToast(`Restart signal sent to worker group '${name}'`)
|
||||
dispatch('reload')
|
||||
} catch (e) {
|
||||
sendUserToast(`Failed to restart worker group: ${e}`, true)
|
||||
}
|
||||
openRestart = false
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span
|
||||
>Are you sure you want to restart all workers in worker group '{name}'? Workers will be
|
||||
gracefully shut down and are expected to be restarted by their supervisor.</span
|
||||
>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<Drawer bind:this={drawer} size="800px">
|
||||
<DrawerContent
|
||||
on:close={() => drawer?.closeDrawer()}
|
||||
@@ -1270,17 +1236,6 @@
|
||||
>
|
||||
Clean cache
|
||||
</Button>
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="subtle"
|
||||
on:click={() => {
|
||||
openRestart = true
|
||||
}}
|
||||
startIcon={{ icon: Power }}
|
||||
destructive
|
||||
>
|
||||
Restart workers
|
||||
</Button>
|
||||
{:else}
|
||||
<Dropdown
|
||||
items={[
|
||||
@@ -1293,13 +1248,6 @@
|
||||
disabled: !config,
|
||||
type: 'delete'
|
||||
},
|
||||
{
|
||||
displayName: 'Restart workers',
|
||||
action: () => {
|
||||
openRestart = true
|
||||
},
|
||||
type: 'delete'
|
||||
},
|
||||
{
|
||||
displayName: 'Delete config',
|
||||
action: () => {
|
||||
|
||||
@@ -227,7 +227,6 @@ class AIChatManager {
|
||||
pendingPrompt?: string,
|
||||
options?: {
|
||||
closeScriptSettings?: boolean
|
||||
lang?: ScriptLang | 'bunnative'
|
||||
}
|
||||
) {
|
||||
if (mode === AIMode.SCRIPT && !tryGetCurrentModel()) return
|
||||
@@ -236,7 +235,7 @@ class AIChatManager {
|
||||
if (mode === AIMode.SCRIPT) {
|
||||
const currentModel = getCurrentModel()
|
||||
const customPrompt = getCombinedCustomPrompt(mode)
|
||||
const lang = options?.lang ?? this.scriptEditorOptions?.lang ?? 'bun'
|
||||
const lang = this.scriptEditorOptions?.lang ?? 'bun'
|
||||
const context = this.contextManager.getSelectedContext()
|
||||
this.systemMessage = prepareScriptSystemMessage(currentModel, lang, {}, customPrompt)
|
||||
this.systemMessage.content = this.systemMessage.content
|
||||
@@ -446,7 +445,10 @@ class AIChatManager {
|
||||
if (!pendingPrompt) return undefined
|
||||
this.pendingPrompt = ''
|
||||
if (this.mode === AIMode.SCRIPT) {
|
||||
return prepareScriptUserMessage(pendingPrompt, this.contextManager.getSelectedContext())
|
||||
return prepareScriptUserMessage(
|
||||
pendingPrompt,
|
||||
this.contextManager.getSelectedContext()
|
||||
)
|
||||
} else if (this.mode === AIMode.FLOW) {
|
||||
return prepareFlowUserMessage(
|
||||
pendingPrompt,
|
||||
@@ -572,9 +574,9 @@ class AIChatManager {
|
||||
} = {}
|
||||
) => {
|
||||
if (options.mode) {
|
||||
this.changeMode(options.mode, undefined, { lang: options.lang })
|
||||
this.changeMode(options.mode)
|
||||
} else {
|
||||
this.changeMode(this.mode, undefined, { lang: options.lang })
|
||||
this.changeMode(this.mode)
|
||||
}
|
||||
if (options.instructions) {
|
||||
this.instructions = options.instructions
|
||||
|
||||
@@ -145,14 +145,6 @@
|
||||
}}
|
||||
options={{ right: 'Disable TLS' }}
|
||||
/>
|
||||
|
||||
<Toggle
|
||||
id="smtp_clicktracking_off"
|
||||
{disabled}
|
||||
bind:checked={$values['smtp_settings'].smtp_clicktracking_off}
|
||||
size="xs"
|
||||
options={{ right: 'Disable click tracking on links' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Test Email -->
|
||||
|
||||
@@ -4,7 +4,7 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.673.0"
|
||||
wmill = ">=1.672.0"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: '3.0.3'
|
||||
|
||||
info:
|
||||
version: 1.673.0
|
||||
version: 1.672.0
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.673.0'
|
||||
ModuleVersion = '1.672.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.673.0"
|
||||
version = "1.672.0"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.673.0",
|
||||
"version": "1.672.0",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.673.0",
|
||||
"version": "1.672.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"sideEffects": false,
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.673.0
|
||||
1.672.0
|
||||
|
||||
Reference in New Issue
Block a user