mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 00:04:10 +00:00
Merge remote-tracking branch 'origin/main' into remove-workspace-drafts
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## [1.721.0](https://github.com/windmill-labs/windmill/compare/v1.720.0...v1.721.0) (2026-06-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* deployed↔draft compare + AI-session draft bar ([#9435](https://github.com/windmill-labs/windmill/issues/9435)) ([b0b330c](https://github.com/windmill-labs/windmill/commit/b0b330c7864d0159af4b0f17dbb3c09bd015145b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** reconcile case-only path drift during sync on case-insensitive filesystems (WIN-2020) ([#9485](https://github.com/windmill-labs/windmill/issues/9485)) ([c258928](https://github.com/windmill-labs/windmill/commit/c258928ab62adc1327913c21556520dfd1e5c24c))
|
||||
* drop archived items from fork compare (spurious 'not visible' warning) ([#9481](https://github.com/windmill-labs/windmill/issues/9481)) ([92c21bb](https://github.com/windmill-labs/windmill/commit/92c21bbe6586f3c285796a98692e976515a629d5))
|
||||
* require auth to view approval details when user_auth_required ([#9482](https://github.com/windmill-labs/windmill/issues/9482)) ([5f41ddd](https://github.com/windmill-labs/windmill/commit/5f41ddd3a592bcd504f94fc99060ca5d79c36190))
|
||||
|
||||
## [1.720.0](https://github.com/windmill-labs/windmill/compare/v1.719.0...v1.720.0) (2026-06-08)
|
||||
|
||||
|
||||
|
||||
Generated
+78
-78
@@ -13782,7 +13782,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -13864,7 +13864,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-ai"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
@@ -13897,7 +13897,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -13910,7 +13910,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -14048,7 +14048,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14071,7 +14071,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14084,7 +14084,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -14110,7 +14110,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -14120,7 +14120,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14137,7 +14137,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"base64 0.22.1",
|
||||
@@ -14159,7 +14159,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -14182,7 +14182,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14198,7 +14198,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14219,7 +14219,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14240,7 +14240,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14254,7 +14254,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -14289,7 +14289,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -14314,7 +14314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"flate2",
|
||||
@@ -14332,7 +14332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -14354,7 +14354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14374,7 +14374,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14405,7 +14405,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -14433,7 +14433,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -14445,7 +14445,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.8.9",
|
||||
@@ -14470,7 +14470,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14484,7 +14484,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"axum 0.8.9",
|
||||
"chrono",
|
||||
@@ -14517,7 +14517,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -14531,7 +14531,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.8.9",
|
||||
@@ -14550,7 +14550,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"aho-corasick",
|
||||
@@ -14651,7 +14651,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@@ -14670,7 +14670,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -14685,7 +14685,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -14709,7 +14709,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -14726,7 +14726,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -14742,7 +14742,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -14763,7 +14763,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -14794,7 +14794,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -14819,7 +14819,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -14853,7 +14853,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -14871,7 +14871,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -14880,7 +14880,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -14892,7 +14892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -14904,7 +14904,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -14916,7 +14916,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -14928,7 +14928,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -14940,7 +14940,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -14951,7 +14951,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -14962,7 +14962,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -14974,7 +14974,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -14985,7 +14985,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15007,7 +15007,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15019,7 +15019,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15033,7 +15033,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -15050,7 +15050,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15063,7 +15063,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -15075,7 +15075,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15093,7 +15093,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -15109,7 +15109,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -15125,7 +15125,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -15136,7 +15136,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15174,7 +15174,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -15212,7 +15212,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"wasm-bindgen",
|
||||
@@ -15223,7 +15223,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15255,7 +15255,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15279,7 +15279,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15312,7 +15312,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-azure"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15345,7 +15345,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15365,7 +15365,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15399,7 +15399,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15435,7 +15435,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15458,7 +15458,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15482,7 +15482,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -15506,7 +15506,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15541,7 +15541,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15569,7 +15569,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -15594,7 +15594,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.0",
|
||||
@@ -15613,7 +15613,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
@@ -15723,7 +15723,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker-volumes"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -87,7 +87,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
+24
-24
@@ -6183,7 +6183,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -6263,7 +6263,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6275,7 +6275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"serde",
|
||||
@@ -6284,7 +6284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6296,7 +6296,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6308,7 +6308,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -6320,7 +6320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6332,7 +6332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6344,7 +6344,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -6355,7 +6355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6366,7 +6366,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6378,7 +6378,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6389,7 +6389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -6411,7 +6411,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6423,7 +6423,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6437,7 +6437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case",
|
||||
@@ -6454,7 +6454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6467,7 +6467,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6479,7 +6479,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6497,7 +6497,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -6513,7 +6513,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6529,7 +6529,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.17",
|
||||
@@ -6561,7 +6561,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6572,7 +6572,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
|
||||
@@ -12,7 +12,7 @@ resolver = "2"
|
||||
members = ["."]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
edition = "2021"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.720.0
|
||||
version: 1.721.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
+1
-1
@@ -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.720.0";
|
||||
export const VERSION = "v1.721.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -1836,6 +1836,247 @@ export interface Skips {
|
||||
includeKey?: boolean | undefined;
|
||||
}
|
||||
|
||||
// Detect paths (and their parent directories) within a single set that differ
|
||||
// only by letter case — e.g. a remote workspace that genuinely holds both
|
||||
// f/Caps/a and f/caps/b. On a case-insensitive filesystem (Windows, default
|
||||
// macOS) these cannot be represented as two distinct files/directories at all,
|
||||
// so we can only warn. Returns one group per collision, each listing the
|
||||
// distinct casings sorted for stable output.
|
||||
//
|
||||
// Only the shallowest clash is reported: when two case-variant folders also
|
||||
// contain same-named files (f/Caps/main.ts + f/caps/main.ts), the folder clash
|
||||
// is the root cause, so the nested per-file group is suppressed rather than
|
||||
// inflating the count with one entry per duplicated leaf.
|
||||
export function findCaseInsensitiveCollisions(
|
||||
paths: Iterable<string>,
|
||||
): string[][] {
|
||||
// lowercased prefix -> set of distinct original casings observed
|
||||
const byLower = new Map<string, Set<string>>();
|
||||
for (const full of paths) {
|
||||
// Compare on normalized forward-slash prefixes so a Windows-style "\" map
|
||||
// key and a remote "/" key collapse to the same prefix.
|
||||
const segs = full.split(/[\\/]/).filter((s) => s.length > 0);
|
||||
let acc = "";
|
||||
for (let i = 0; i < segs.length; i++) {
|
||||
// Accumulate every directory prefix as well as the full file path, so a
|
||||
// "f/Caps" vs "f/caps" folder clash is caught even when the leaf files
|
||||
// (e.g. a.ts vs b.ts) don't themselves collide.
|
||||
acc = i === 0 ? segs[i] : `${acc}/${segs[i]}`;
|
||||
const lower = acc.toLowerCase();
|
||||
let set = byLower.get(lower);
|
||||
if (!set) {
|
||||
set = new Set();
|
||||
byLower.set(lower, set);
|
||||
}
|
||||
set.add(acc);
|
||||
}
|
||||
}
|
||||
const collidingLowers = new Set<string>();
|
||||
for (const [lower, set] of byLower) {
|
||||
if (set.size > 1) collidingLowers.add(lower);
|
||||
}
|
||||
const collisions: string[][] = [];
|
||||
for (const lower of collidingLowers) {
|
||||
// Drop this group if any ancestor prefix is itself a collision — the
|
||||
// shallower folder clash already names the root cause.
|
||||
const parts = lower.split("/");
|
||||
let hasCollidingAncestor = false;
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
if (collidingLowers.has(parts.slice(0, i).join("/"))) {
|
||||
hasCollidingAncestor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasCollidingAncestor) {
|
||||
collisions.push([...byLower.get(lower)!].sort());
|
||||
}
|
||||
}
|
||||
return collisions;
|
||||
}
|
||||
|
||||
type CaseTrieNode = {
|
||||
// lowercased segment -> child, recording the canonical (server) casing and
|
||||
// whether the server holds more than one casing of that segment (ambiguous).
|
||||
children: Map<
|
||||
string,
|
||||
{ canonical: string; ambiguous: boolean; node: CaseTrieNode }
|
||||
>;
|
||||
};
|
||||
|
||||
// Rewrite `localMap` keys to the canonical casing recorded on the server
|
||||
// (`remoteMap`) when they differ only by letter case. This is the core
|
||||
// WIN-2020 fix: on a case-insensitive filesystem a folder such as `f/Caps`
|
||||
// can have its on-disk casing silently drift (e.g. to `f/caps`) — Windows
|
||||
// stores whatever case the directory was first created with and reports that
|
||||
// from readdir, regardless of the server's path. Without this, the diff sees
|
||||
// the drifted local path as an entirely different item and emits a destructive
|
||||
// "delete f/Caps + add f/caps" pair, so a single capitalized folder appears to
|
||||
// vanish and a lowercase clone shows up out of nowhere. Adopting the server
|
||||
// casing collapses that phantom and leaves the canonical path on the server
|
||||
// untouched.
|
||||
//
|
||||
// Canonicalization is segment-by-segment against a trie of remote paths, so it
|
||||
// also applies the server's folder casing to brand-new local files that have no
|
||||
// exact remote match (e.g. adding f/caps/New.ts under a drifted f/Caps folder
|
||||
// becomes f/Caps/New.ts) — otherwise the push would recreate the case-only
|
||||
// collision the fix is meant to prevent. A segment is only adopted when the
|
||||
// server casing is unambiguous; at the first ambiguous or unknown segment the
|
||||
// remainder of the path keeps its local casing.
|
||||
//
|
||||
// Returns the rewritten map, the per-key rewrites, and any genuinely ambiguous
|
||||
// server-side groups (two distinct remote paths differing only by case) — those
|
||||
// can't be canonicalized to a single target and are left for the caller to warn
|
||||
// about.
|
||||
export function canonicalizeCaseInsensitiveKeys(
|
||||
localMap: Record<string, string>,
|
||||
remoteMap: Record<string, string>,
|
||||
): {
|
||||
map: Record<string, string>;
|
||||
ambiguous: string[][];
|
||||
rewritten: { from: string; to: string }[];
|
||||
} {
|
||||
const root: CaseTrieNode = { children: new Map() };
|
||||
for (const k of Object.keys(remoteMap)) {
|
||||
let node = root;
|
||||
for (const seg of k.split(/[\\/]/)) {
|
||||
if (seg.length === 0) continue;
|
||||
const lk = seg.toLowerCase();
|
||||
let entry = node.children.get(lk);
|
||||
if (!entry) {
|
||||
entry = { canonical: seg, ambiguous: false, node: { children: new Map() } };
|
||||
node.children.set(lk, entry);
|
||||
} else if (entry.canonical !== seg) {
|
||||
entry.ambiguous = true;
|
||||
}
|
||||
node = entry.node;
|
||||
}
|
||||
}
|
||||
|
||||
const out: Record<string, string> = {};
|
||||
const rewritten: { from: string; to: string }[] = [];
|
||||
for (const [k, v] of Object.entries(localMap)) {
|
||||
// Preserve the key's own separator style so the rewritten key still matches
|
||||
// the rest of the map (and round-trips through push) on every platform.
|
||||
const sep = k.includes("\\") ? "\\" : "/";
|
||||
const segs = k.split(/[\\/]/);
|
||||
const canonSegs: string[] = [];
|
||||
let node: CaseTrieNode | undefined = root;
|
||||
let changed = false;
|
||||
for (const seg of segs) {
|
||||
if (seg.length === 0) {
|
||||
canonSegs.push(seg);
|
||||
continue;
|
||||
}
|
||||
const entry = node?.children.get(seg.toLowerCase());
|
||||
if (entry && !entry.ambiguous) {
|
||||
if (entry.canonical !== seg) changed = true;
|
||||
canonSegs.push(entry.canonical);
|
||||
node = entry.node;
|
||||
} else {
|
||||
// No unambiguous server guidance for this segment: keep the local
|
||||
// casing here and below (deeper server structure is unknown).
|
||||
canonSegs.push(seg);
|
||||
node = undefined;
|
||||
}
|
||||
}
|
||||
const canonKey = canonSegs.join(sep);
|
||||
if (changed && canonKey !== k) {
|
||||
out[canonKey] = v;
|
||||
rewritten.push({ from: k, to: canonKey });
|
||||
} else {
|
||||
out[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
map: out,
|
||||
ambiguous: findCaseInsensitiveCollisions(Object.keys(remoteMap)),
|
||||
rewritten,
|
||||
};
|
||||
}
|
||||
|
||||
// Summarize case-only key rewrites by their differing path prefix (typically a
|
||||
// folder such as f/caps -> f/Caps) so a folder whose casing drifted is reported
|
||||
// once instead of once per contained file.
|
||||
export function summarizeCaseRewrites(
|
||||
rewritten: { from: string; to: string }[],
|
||||
): string[] {
|
||||
const seen = new Set<string>();
|
||||
const out: string[] = [];
|
||||
for (const { from, to } of rewritten) {
|
||||
const fromSegs = from.split(/[\\/]/);
|
||||
const toSegs = to.split(/[\\/]/);
|
||||
// Find the shortest prefix at which the two casings first differ; that is
|
||||
// the folder (or file) whose casing actually changed.
|
||||
let i = 0;
|
||||
while (
|
||||
i < fromSegs.length &&
|
||||
i < toSegs.length &&
|
||||
fromSegs[i] === toSegs[i]
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
const fromPrefix = fromSegs.slice(0, i + 1).join("/");
|
||||
const toPrefix = toSegs.slice(0, i + 1).join("/");
|
||||
const key = `${fromPrefix} -> ${toPrefix}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
out.push(key);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Emit a single grouped warning for case-only collisions that cannot be
|
||||
// represented on a case-insensitive filesystem (two distinct server paths
|
||||
// differing only by case). Unlike the drift handled by
|
||||
// canonicalizeCaseInsensitiveKeys, these require the user to rename one side.
|
||||
function warnUnrepresentableCaseCollisions(collisions: string[][]): void {
|
||||
if (collisions.length === 0) return;
|
||||
const groups = collisions.map((g) => ` - ${g.join(" <-> ")}`).join("\n");
|
||||
log.warn(
|
||||
`Found ${collisions.length} path(s) that differ only by letter case:\n` +
|
||||
`${groups}\n` +
|
||||
`On case-insensitive filesystems (Windows, default macOS) these collapse ` +
|
||||
`into a single file/directory and cannot both be synced. Rename one side ` +
|
||||
`to a distinct path to make the tree sync reliably across platforms.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Probe (and cache) whether `dir` lives on a case-insensitive filesystem.
|
||||
// Auto-detected by round-tripping a probe file under two casings, with an
|
||||
// explicit WMILL_CASE_INSENSITIVE_FS=true/false override so Windows behaviour
|
||||
// can be forced (or emulated for cross-platform repos / tests) on any host.
|
||||
let _caseInsensitiveFsCache: boolean | undefined;
|
||||
export async function isCaseInsensitiveFilesystem(
|
||||
dir: string,
|
||||
): Promise<boolean> {
|
||||
const override = (process.env.WMILL_CASE_INSENSITIVE_FS ?? "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (override === "true" || override === "1") return true;
|
||||
if (override === "false" || override === "0") return false;
|
||||
if (_caseInsensitiveFsCache !== undefined) return _caseInsensitiveFsCache;
|
||||
let result = false;
|
||||
try {
|
||||
const upper = path.join(dir, `.wmill-CASEPROBE-${process.pid}.tmp`);
|
||||
const lower = path.join(dir, `.wmill-caseprobe-${process.pid}.tmp`);
|
||||
await writeFile(upper, "", "utf-8");
|
||||
try {
|
||||
await stat(lower);
|
||||
result = true; // lowercase name resolves to the file we wrote uppercase
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
await rm(upper).catch(() => {});
|
||||
await rm(lower).catch(() => {});
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
_caseInsensitiveFsCache = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
async function compareDynFSElement(
|
||||
els1: DynFSElement,
|
||||
els2: DynFSElement | undefined,
|
||||
@@ -1848,14 +2089,55 @@ async function compareDynFSElement(
|
||||
specificItems?: SpecificItemsConfig,
|
||||
branchOverride?: string,
|
||||
isEls1Remote?: boolean,
|
||||
caseInsensitiveFs?: boolean,
|
||||
): Promise<{ changes: Change[]; localMap: Record<string, string> }> {
|
||||
const [m1, m2] = els2
|
||||
let [m1, m2] = els2
|
||||
? await Promise.all([
|
||||
elementsToMap(els1, ignore, json, skips, specificItems, branchOverride, isEls1Remote),
|
||||
elementsToMap(els2, ignore, json, skips, specificItems, branchOverride, !isEls1Remote),
|
||||
])
|
||||
: [await elementsToMap(els1, ignore, json, skips, specificItems, branchOverride, isEls1Remote), {}];
|
||||
|
||||
// Reconcile letter-case differences between the local tree and the
|
||||
// authoritative server casing. Only meaningful for an actual two-sided diff
|
||||
// (els2 defined) where we know which side is the remote.
|
||||
if (els2 && isEls1Remote !== undefined) {
|
||||
const remoteMap = isEls1Remote ? m1 : m2;
|
||||
|
||||
// Always warn about server paths that differ only by case (e.g. f/Caps and
|
||||
// f/caps as two distinct items). These cannot coexist on a case-insensitive
|
||||
// filesystem, so flag them on every platform — a Linux author needs to know
|
||||
// their tree won't round-trip for a Windows/macOS teammate.
|
||||
warnUnrepresentableCaseCollisions(
|
||||
findCaseInsensitiveCollisions(Object.keys(remoteMap)),
|
||||
);
|
||||
|
||||
// On a case-insensitive filesystem, the local on-disk casing of a folder
|
||||
// can drift from the server's (Windows reports the case the directory was
|
||||
// first created with). Rewrite those drifted local keys to the server
|
||||
// casing so the diff treats them as the same item instead of a destructive
|
||||
// delete+add pair. This is the WIN-2020 fix.
|
||||
if (caseInsensitiveFs) {
|
||||
const { map, rewritten } = canonicalizeCaseInsensitiveKeys(
|
||||
isEls1Remote ? m2 : m1,
|
||||
remoteMap,
|
||||
);
|
||||
if (isEls1Remote) {
|
||||
m2 = map;
|
||||
} else {
|
||||
m1 = map;
|
||||
}
|
||||
const summary = summarizeCaseRewrites(rewritten);
|
||||
if (summary.length > 0) {
|
||||
log.info(
|
||||
`Reconciled ${summary.length} local path(s) to the server's casing ` +
|
||||
`(case-insensitive filesystem):\n` +
|
||||
summary.map((s) => ` ${s}`).join("\n"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const changes: Change[] = [];
|
||||
|
||||
function parseYaml(k: string, v: string) {
|
||||
@@ -2613,6 +2895,7 @@ export async function pull(
|
||||
specificItems,
|
||||
wsNameForFiles,
|
||||
true, // els1 (remote) is the remote source
|
||||
await isCaseInsensitiveFilesystem(process.cwd()),
|
||||
);
|
||||
|
||||
log.info(
|
||||
@@ -3182,6 +3465,14 @@ function removeSuffix(str: string, suffix: string) {
|
||||
return str.slice(0, str.length - suffix.length);
|
||||
}
|
||||
|
||||
// Shown after a `wmill sync push --dry-run` preview that has changes. `sync push`
|
||||
// deploys to the remote workspace and is destructive (it overwrites and prunes
|
||||
// remote items that differ from or are absent locally), so the preview reminds
|
||||
// the caller — especially an AI agent that ran the dry-run to inspect changes —
|
||||
// to get explicit user confirmation before applying it for real.
|
||||
const SYNC_PUSH_DESTRUCTIVE_WARNING =
|
||||
"`wmill sync push` is destructive: applying it deploys these changes to the remote workspace and overwrites or deletes remote items that differ from or are absent locally — this is not automatically reversible. If you are an AI agent, do NOT run `wmill sync push` (without --dry-run) until the user has explicitly confirmed this deploy, unless your custom instructions explicitly allow bypassing that confirmation.";
|
||||
|
||||
export async function push(
|
||||
opts: GlobalOptions & SyncOptions & { repository?: string; branch?: string; acceptOverridingPermissionedAsWithSelf?: boolean },
|
||||
) {
|
||||
@@ -3361,6 +3652,7 @@ export async function push(
|
||||
specificItems,
|
||||
wsNameForFiles,
|
||||
false, // els1 (local) is not the remote source
|
||||
await isCaseInsensitiveFilesystem(process.cwd()),
|
||||
);
|
||||
|
||||
// Detect resources/variables that the local config flags as ws_specific
|
||||
@@ -3717,6 +4009,9 @@ export async function push(
|
||||
: {}),
|
||||
})),
|
||||
total: changes.length,
|
||||
...(changes.length > 0
|
||||
? { warning: SYNC_PUSH_DESTRUCTIVE_WARNING }
|
||||
: {}),
|
||||
};
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -3760,6 +4055,7 @@ export async function push(
|
||||
|
||||
if (opts.dryRun) {
|
||||
log.info(colors.gray(`Dry run complete.`));
|
||||
log.warn(colors.yellow(`\n⚠ ${SYNC_PUSH_DESTRUCTIVE_WARNING}`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@ export const WM_FORK_PREFIX = "wm-fork";
|
||||
// (e.g. utils.ts) can read it without importing main.ts and creating a circular
|
||||
// dependency (main → workspace → utils → main) that triggers a TDZ.
|
||||
// Re-exported from main.ts for backwards compatibility.
|
||||
export const VERSION = "1.720.0";
|
||||
export const VERSION = "1.721.0";
|
||||
|
||||
@@ -5266,10 +5266,10 @@ Text/HTML/inline parts are placed inline in \`body\` as strings.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
After writing, tell the user they can run these commands (do NOT run them yourself):
|
||||
\`wmill sync push\` deploys local changes to the workspace and can be destructive to remote state — only suggest/run it when the user explicitly asks to deploy/publish/push, not when they say "run", "try", or "test". \`sync pull\` is safe to run yourself — it never mutates remote state, though it does overwrite local files to match the remote (use \`sync pull --dry-run\` to only preview).
|
||||
|
||||
\`\`\`bash
|
||||
# Push trigger configuration
|
||||
# Push trigger configuration — only when the user explicitly asks to deploy
|
||||
wmill sync push
|
||||
|
||||
# Pull triggers from Windmill
|
||||
@@ -5317,10 +5317,10 @@ Windmill uses 6-field cron expressions (includes seconds):
|
||||
|
||||
## CLI Commands
|
||||
|
||||
After writing, tell the user they can run these commands (do NOT run them yourself):
|
||||
\`wmill sync push\` deploys local changes to the workspace and can be destructive to remote state — only suggest/run it when the user explicitly asks to deploy/publish/push, not when they say "run", "try", or "test". The commands below never mutate remote state, so they're safe to run yourself — note that \`sync pull\` does overwrite local files to match the remote (use \`sync pull --dry-run\` to only preview), while \`schedule\` just lists.
|
||||
|
||||
\`\`\`bash
|
||||
# Push schedules to Windmill
|
||||
# Push schedules to Windmill — only when the user explicitly asks to deploy
|
||||
wmill sync push
|
||||
|
||||
# Pull schedules from Windmill
|
||||
@@ -5574,7 +5574,8 @@ wmill resource-type list --schema
|
||||
# Get specific resource type schema
|
||||
wmill resource-type get postgresql
|
||||
|
||||
# Push resources (tell the user to run this, do NOT run it yourself)
|
||||
# Push resources to Windmill — deploys to the workspace and can be destructive to
|
||||
# remote state, so only run it when the user explicitly asks to deploy/publish/push
|
||||
wmill sync push
|
||||
\`\`\`
|
||||
`,
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
import {
|
||||
findCaseInsensitiveCollisions,
|
||||
canonicalizeCaseInsensitiveKeys,
|
||||
summarizeCaseRewrites,
|
||||
} from "../src/commands/sync/sync.ts";
|
||||
|
||||
// =============================================================================
|
||||
// Case-insensitive sync handling (WIN-2020)
|
||||
//
|
||||
// Windmill paths are case-sensitive, but Windows and the default macOS setup
|
||||
// use case-insensitive filesystems. The real-world failure is NOT a user
|
||||
// deliberately authoring both f/Caps and f/caps — it is a single capitalized
|
||||
// folder whose on-disk casing drifts (Windows reports the case the directory
|
||||
// was first created with), so the diff sees a brand-new lowercase path and a
|
||||
// destructive delete of the real one. These tests pin:
|
||||
// 1. findCaseInsensitiveCollisions — warn about genuinely unrepresentable
|
||||
// server-side collisions (two distinct remote paths differing only by
|
||||
// case).
|
||||
// 2. canonicalizeCaseInsensitiveKeys — the fix: rewrite drifted local keys
|
||||
// to the server's casing so no phantom delete+add is produced.
|
||||
// =============================================================================
|
||||
|
||||
function normalize(groups: string[][]): string[][] {
|
||||
return groups
|
||||
.map((g) => [...g])
|
||||
.sort((a, b) => a.join().localeCompare(b.join()));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// findCaseInsensitiveCollisions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("collision detection: sibling folders differing only by case", () => {
|
||||
const collisions = findCaseInsensitiveCollisions([
|
||||
"f/Caps/a.script.ts",
|
||||
"f/caps/b.script.ts",
|
||||
]);
|
||||
expect(normalize(collisions)).toEqual([["f/Caps", "f/caps"]]);
|
||||
});
|
||||
|
||||
test("collision detection: leaf files differing only by case", () => {
|
||||
const collisions = findCaseInsensitiveCollisions([
|
||||
"f/team/Report.script.ts",
|
||||
"f/team/report.script.ts",
|
||||
]);
|
||||
expect(normalize(collisions)).toEqual([
|
||||
["f/team/Report.script.ts", "f/team/report.script.ts"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("collision detection: none for case-consistent distinct paths", () => {
|
||||
const collisions = findCaseInsensitiveCollisions([
|
||||
"f/caps/foo.script.ts",
|
||||
"f/caps/bar.script.ts",
|
||||
"f/other/baz.script.ts",
|
||||
]);
|
||||
expect(collisions).toEqual([]);
|
||||
});
|
||||
|
||||
test("collision detection: normalizes mixed forward/back slashes", () => {
|
||||
const collisions = findCaseInsensitiveCollisions([
|
||||
"f\\Caps\\a.script.ts",
|
||||
"f/caps/b.script.ts",
|
||||
]);
|
||||
expect(normalize(collisions)).toEqual([["f/Caps", "f/caps"]]);
|
||||
});
|
||||
|
||||
test("collision detection: reports only the shallowest clash for same-named leaves", () => {
|
||||
// Two case-variant folders that ALSO hold a same-named file must report the
|
||||
// single folder clash, not the folder group plus a nested per-file group.
|
||||
const collisions = findCaseInsensitiveCollisions([
|
||||
"f/Caps/main.script.ts",
|
||||
"f/caps/main.script.ts",
|
||||
]);
|
||||
expect(normalize(collisions)).toEqual([["f/Caps", "f/caps"]]);
|
||||
});
|
||||
|
||||
test("collision detection: groups three distinct casings together", () => {
|
||||
const collisions = findCaseInsensitiveCollisions([
|
||||
"f/caps/a.script.ts",
|
||||
"f/CAPS/b.script.ts",
|
||||
"f/Caps/c.script.ts",
|
||||
]);
|
||||
expect(normalize(collisions)).toEqual([["f/CAPS", "f/Caps", "f/caps"]]);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// canonicalizeCaseInsensitiveKeys (the WIN-2020 fix)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("canonicalize: drifted local folder casing adopts the server casing", () => {
|
||||
// Server (remote) is authoritative: f/Caps. The local tree drifted to
|
||||
// f/caps on a case-insensitive FS.
|
||||
const remote = {
|
||||
"f/Caps/x.script.ts": "content",
|
||||
"f/Caps/x.script.yaml": "meta",
|
||||
};
|
||||
const local = {
|
||||
"f/caps/x.script.ts": "content",
|
||||
"f/caps/x.script.yaml": "meta",
|
||||
};
|
||||
const { map, ambiguous, rewritten } = canonicalizeCaseInsensitiveKeys(
|
||||
local,
|
||||
remote,
|
||||
);
|
||||
// Local keys are rewritten to the server casing, so a subsequent exact-key
|
||||
// diff sees identical paths — no phantom delete+add.
|
||||
expect(Object.keys(map).sort()).toEqual([
|
||||
"f/Caps/x.script.ts",
|
||||
"f/Caps/x.script.yaml",
|
||||
]);
|
||||
expect(ambiguous).toEqual([]);
|
||||
expect(rewritten).toEqual([
|
||||
{ from: "f/caps/x.script.ts", to: "f/Caps/x.script.ts" },
|
||||
{ from: "f/caps/x.script.yaml", to: "f/Caps/x.script.yaml" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("canonicalize: preserves content values while rewriting keys", () => {
|
||||
const remote = { "f/MyFolder/Script.script.ts": "remote" };
|
||||
const local = { "f/myfolder/Script.script.ts": "LOCAL EDIT" };
|
||||
const { map } = canonicalizeCaseInsensitiveKeys(local, remote);
|
||||
expect(map["f/MyFolder/Script.script.ts"]).toEqual("LOCAL EDIT");
|
||||
expect(map["f/myfolder/Script.script.ts"]).toBeUndefined();
|
||||
});
|
||||
|
||||
test("canonicalize: leaves keys with no case-insensitive remote match", () => {
|
||||
const remote = { "f/Caps/x.script.ts": "a" };
|
||||
const local = {
|
||||
"f/Caps/x.script.ts": "a",
|
||||
"f/brand_new/y.script.ts": "b", // genuinely local-only add
|
||||
};
|
||||
const { map, rewritten } = canonicalizeCaseInsensitiveKeys(local, remote);
|
||||
expect(rewritten).toEqual([]);
|
||||
expect(Object.keys(map).sort()).toEqual([
|
||||
"f/Caps/x.script.ts",
|
||||
"f/brand_new/y.script.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
test("canonicalize: does NOT rewrite when the server casing is ambiguous", () => {
|
||||
// The server itself holds two paths differing only by case — we must not
|
||||
// silently pick one. Leave the local key untouched and report the ambiguity.
|
||||
const remote = {
|
||||
"f/Caps/x.script.ts": "a",
|
||||
"f/caps/x.script.ts": "b",
|
||||
};
|
||||
const local = { "f/CAPS/x.script.ts": "local" };
|
||||
const { map, ambiguous, rewritten } = canonicalizeCaseInsensitiveKeys(
|
||||
local,
|
||||
remote,
|
||||
);
|
||||
expect(rewritten).toEqual([]);
|
||||
expect(Object.keys(map)).toEqual(["f/CAPS/x.script.ts"]);
|
||||
// Reported as the shallowest (folder) clash, not the nested per-file group.
|
||||
expect(normalize(ambiguous)).toEqual([["f/Caps", "f/caps"]]);
|
||||
});
|
||||
|
||||
test("canonicalize: new local file under a drifted folder adopts the server folder casing", () => {
|
||||
// Regression for the P1 review finding: a brand-new local file has no exact
|
||||
// remote match, but it still lives under a folder whose casing drifted. It
|
||||
// must inherit the server's folder casing (f/Caps), otherwise push would
|
||||
// create f/caps/New beside f/Caps/* and reintroduce the case-only collision.
|
||||
const remote = { "f/Caps/Existing.script.ts": "remote" };
|
||||
const local = {
|
||||
"f/caps/Existing.script.ts": "remote", // drifted, existing
|
||||
"f/caps/New.script.ts": "brand new", // drifted folder, local-only file
|
||||
};
|
||||
const { map, rewritten } = canonicalizeCaseInsensitiveKeys(local, remote);
|
||||
expect(Object.keys(map).sort()).toEqual([
|
||||
"f/Caps/Existing.script.ts",
|
||||
"f/Caps/New.script.ts",
|
||||
]);
|
||||
expect(map["f/Caps/New.script.ts"]).toEqual("brand new");
|
||||
expect(rewritten).toContainEqual({
|
||||
from: "f/caps/New.script.ts",
|
||||
to: "f/Caps/New.script.ts",
|
||||
});
|
||||
});
|
||||
|
||||
test("canonicalize: stops at the first segment with no server guidance", () => {
|
||||
// Only the folder prefix that exists on the server is canonicalized; deeper
|
||||
// local-only directories keep their own casing.
|
||||
const remote = { "f/Caps/x.script.ts": "a" };
|
||||
const local = { "f/caps/SubDir/y.script.ts": "b" };
|
||||
const { map } = canonicalizeCaseInsensitiveKeys(local, remote);
|
||||
expect(Object.keys(map)).toEqual(["f/Caps/SubDir/y.script.ts"]);
|
||||
});
|
||||
|
||||
test("canonicalize: identical casing is a no-op", () => {
|
||||
const remote = { "f/Caps/x.script.ts": "a" };
|
||||
const local = { "f/Caps/x.script.ts": "a" };
|
||||
const { map, rewritten, ambiguous } = canonicalizeCaseInsensitiveKeys(
|
||||
local,
|
||||
remote,
|
||||
);
|
||||
expect(rewritten).toEqual([]);
|
||||
expect(ambiguous).toEqual([]);
|
||||
expect(map).toEqual({ "f/Caps/x.script.ts": "a" });
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// summarizeCaseRewrites
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("summarize: collapses per-file rewrites into one folder entry", () => {
|
||||
const summary = summarizeCaseRewrites([
|
||||
{ from: "f/caps/x.script.ts", to: "f/Caps/x.script.ts" },
|
||||
{ from: "f/caps/x.script.yaml", to: "f/Caps/x.script.yaml" },
|
||||
{ from: "f/caps/y.script.ts", to: "f/Caps/y.script.ts" },
|
||||
]);
|
||||
expect(summary).toEqual(["f/caps -> f/Caps"]);
|
||||
});
|
||||
|
||||
test("summarize: reports a leaf-file casing change at file granularity", () => {
|
||||
const summary = summarizeCaseRewrites([
|
||||
{ from: "f/team/report.script.ts", to: "f/team/Report.script.ts" },
|
||||
]);
|
||||
expect(summary).toEqual([
|
||||
"f/team/report.script.ts -> f/team/Report.script.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
test("summarize: reports independent folder drifts separately", () => {
|
||||
const summary = summarizeCaseRewrites([
|
||||
{ from: "f/caps/x.script.ts", to: "f/Caps/x.script.ts" },
|
||||
{ from: "u/alice/y.script.ts", to: "u/Alice/y.script.ts" },
|
||||
]);
|
||||
expect(summary.sort()).toEqual(["f/caps -> f/Caps", "u/alice -> u/Alice"]);
|
||||
});
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import * as path from "node:path";
|
||||
import { writeFile, readFile, stat } from "node:fs/promises";
|
||||
import { writeFile, readFile, stat, rename } from "node:fs/promises";
|
||||
import { withTestBackend } from "./test_backend.ts";
|
||||
import { addWorkspace } from "../workspace.ts";
|
||||
import { parseJsonFromCLIOutput } from "./test_config_helpers.ts";
|
||||
@@ -686,6 +686,173 @@ excludes: []
|
||||
});
|
||||
});
|
||||
|
||||
// The core WIN-2020 reproduction. The real failure is NOT a user authoring
|
||||
// both f/Caps and f/caps — it is a SINGLE capitalized folder whose on-disk
|
||||
// casing drifts on a case-insensitive filesystem (Windows stores/reports the
|
||||
// case the directory was first created with). The diff then sees a brand-new
|
||||
// lowercase path plus a destructive delete of the real one.
|
||||
//
|
||||
// This test runs on BOTH the Linux and Windows CI jobs:
|
||||
// - On the Windows runner (real case-insensitive NTFS) the CLI auto-detects
|
||||
// case-insensitivity via its filesystem probe — no override, real behavior.
|
||||
// - On Linux (case-sensitive) we reproduce the drift with an explicit rename
|
||||
// and force the same code path with WMILL_CASE_INSENSITIVE_FS=true, and we
|
||||
// additionally assert that WITHOUT the fix the destructive phantom appears
|
||||
// (which can only be observed on a case-sensitive FS).
|
||||
test("Mixed Case Paths: case-only folder drift reconciles to server casing (WIN-2020)", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
await setupWorkspaceProfile(backend);
|
||||
|
||||
await createFolder(backend, "Caps");
|
||||
await createScript(
|
||||
backend,
|
||||
"f/Caps/MyScript",
|
||||
'export async function main() { return "drift repro"; }',
|
||||
"Drift Script"
|
||||
);
|
||||
|
||||
await writeFile(
|
||||
path.join(tempDir, "wmill.yaml"),
|
||||
`defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
excludes: []
|
||||
`,
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
const pullResult = await backend.runCLICommand(["sync", "pull", "--yes"], tempDir);
|
||||
expect(pullResult.code).toEqual(0);
|
||||
|
||||
const upperDir = path.join(tempDir, "f", "Caps");
|
||||
const lowerDir = path.join(tempDir, "f", "caps");
|
||||
const pulledExists = await stat(path.join(upperDir, "MyScript.ts"))
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
expect(pulledExists).toBeTruthy();
|
||||
|
||||
// Simulate the on-disk casing drift (a no-op-in-spirit case-only rename).
|
||||
// On Windows this is a real case-only rename of the same directory; on
|
||||
// Linux it produces a genuinely lowercase sibling.
|
||||
await rename(upperDir, lowerDir);
|
||||
|
||||
const onWindows = process.platform === "win32";
|
||||
|
||||
// Without case-insensitive handling, the drift is a destructive
|
||||
// delete(f/Caps) + add(f/caps) phantom. Only observable on a
|
||||
// case-sensitive FS (Linux), where the override defaults off.
|
||||
if (!onWindows) {
|
||||
const buggy = await backend.runCLICommand(
|
||||
["sync", "push", "--yes", "--dry-run", "--json-output"],
|
||||
tempDir
|
||||
);
|
||||
expect(buggy.code).toEqual(0);
|
||||
const buggyChanges = parseJsonFromCLIOutput(buggy.stdout).changes || [];
|
||||
const hasDelete = buggyChanges.some(
|
||||
(c: any) => c.type === "deleted" && c.path.replace(/\\/g, "/").startsWith("f/Caps/")
|
||||
);
|
||||
const hasAdd = buggyChanges.some(
|
||||
(c: any) => c.type === "added" && c.path.replace(/\\/g, "/").startsWith("f/caps/")
|
||||
);
|
||||
expect(hasDelete).toBeTruthy();
|
||||
expect(hasAdd).toBeTruthy();
|
||||
}
|
||||
|
||||
// With case-insensitive handling in effect (auto on Windows via the FS
|
||||
// probe, forced via env on Linux) the drifted local casing is reconciled
|
||||
// to the server's casing, so the push is a clean no-op.
|
||||
if (!onWindows) process.env.WMILL_CASE_INSENSITIVE_FS = "true";
|
||||
try {
|
||||
const fixed = await backend.runCLICommand(
|
||||
["sync", "push", "--yes", "--dry-run", "--json-output"],
|
||||
tempDir
|
||||
);
|
||||
expect(fixed.code).toEqual(0);
|
||||
const fixedChanges = parseJsonFromCLIOutput(fixed.stdout).changes || [];
|
||||
if (fixedChanges.length !== 0) {
|
||||
console.error(
|
||||
"Expected no changes after reconciliation, got:",
|
||||
JSON.stringify(fixedChanges, null, 2)
|
||||
);
|
||||
}
|
||||
expect(fixedChanges.length).toEqual(0);
|
||||
|
||||
// P1 regression: a brand-new item added under the drifted (lowercase)
|
||||
// folder must be pushed under the server's folder casing (f/Caps), not
|
||||
// recreate the case-only collision as f/caps. A self-contained variable
|
||||
// YAML is used so the assertion targets path canonicalization without
|
||||
// dragging in script lock/metadata generation.
|
||||
await writeFile(
|
||||
path.join(lowerDir, "NewVar.variable.yaml"),
|
||||
`value: hello\nis_secret: false\ndescription: new under drifted folder\nis_oauth: false\n`,
|
||||
"utf-8"
|
||||
);
|
||||
const added = await backend.runCLICommand(
|
||||
["sync", "push", "--yes", "--dry-run", "--json-output"],
|
||||
tempDir
|
||||
);
|
||||
expect(added.code).toEqual(0);
|
||||
const addedChanges = parseJsonFromCLIOutput(added.stdout).changes || [];
|
||||
const addedPaths = addedChanges.map((c: any) =>
|
||||
c.path.replace(/\\/g, "/")
|
||||
);
|
||||
expect(
|
||||
addedPaths.some((p: string) => p === "f/Caps/NewVar.variable.yaml")
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
addedPaths.some((p: string) => p.startsWith("f/caps/"))
|
||||
).toBeFalsy();
|
||||
} finally {
|
||||
if (!onWindows) delete process.env.WMILL_CASE_INSENSITIVE_FS;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// A genuine, unrepresentable server-side collision: two DISTINCT server folders
|
||||
// that differ only by case (f/Caps and f/caps). These cannot both exist on a
|
||||
// case-insensitive filesystem, so the CLI must warn. Skipped on Windows, where
|
||||
// the pull physically cannot lay both folders down; the warning string itself
|
||||
// is exercised platform-independently by the unit tests.
|
||||
const collisionTest = process.platform === "win32" ? test.skip : test;
|
||||
collisionTest("Mixed Case Paths: distinct server folders differing only by case warn", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
await setupWorkspaceProfile(backend);
|
||||
|
||||
await createFolder(backend, "Caps");
|
||||
await createFolder(backend, "caps");
|
||||
await createScript(
|
||||
backend,
|
||||
"f/Caps/upper",
|
||||
'export async function main() { return "upper"; }',
|
||||
"Upper"
|
||||
);
|
||||
await createScript(
|
||||
backend,
|
||||
"f/caps/lower",
|
||||
'export async function main() { return "lower"; }',
|
||||
"Lower"
|
||||
);
|
||||
|
||||
await writeFile(
|
||||
path.join(tempDir, "wmill.yaml"),
|
||||
`defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
excludes: []
|
||||
`,
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
const pullResult = await backend.runCLICommand(["sync", "pull", "--yes"], tempDir);
|
||||
expect(pullResult.code).toEqual(0);
|
||||
|
||||
const combinedOutput = pullResult.stdout + pullResult.stderr;
|
||||
expect(combinedOutput.includes("differ only by letter case")).toBeTruthy();
|
||||
expect(combinedOutput.includes("f/Caps")).toBeTruthy();
|
||||
expect(combinedOutput.includes("f/caps")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test("Mixed Case Paths: CamelCase folder names with numbers", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
await setupWorkspaceProfile(backend);
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.720.0",
|
||||
"version": "1.721.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.720.0",
|
||||
"version": "1.721.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.720.0",
|
||||
"version": "1.721.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev:ui-builder": "mv static/ui_builder static/ui_builder.dev-disabled 2>/dev/null || true ; trap 'mv static/ui_builder.dev-disabled static/ui_builder 2>/dev/null || true' EXIT ; vite dev",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.720.0"
|
||||
wmill = ">=1.721.0"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: '3.0.3'
|
||||
|
||||
info:
|
||||
version: 1.720.0
|
||||
version: 1.721.0
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.720.0'
|
||||
ModuleVersion = '1.721.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.720.0"
|
||||
version = "1.721.0"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -563,7 +563,8 @@ wmill resource-type list --schema
|
||||
# Get specific resource type schema
|
||||
wmill resource-type get postgresql
|
||||
|
||||
# Push resources (tell the user to run this, do NOT run it yourself)
|
||||
# Push resources to Windmill — deploys to the workspace and can be destructive to
|
||||
# remote state, so only run it when the user explicitly asks to deploy/publish/push
|
||||
wmill sync push
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
@@ -242,6 +242,7 @@ wmill resource-type list --schema
|
||||
# Get specific resource type schema
|
||||
wmill resource-type get postgresql
|
||||
|
||||
# Push resources (tell the user to run this, do NOT run it yourself)
|
||||
# Push resources to Windmill — deploys to the workspace and can be destructive to
|
||||
# remote state, so only run it when the user explicitly asks to deploy/publish/push
|
||||
wmill sync push
|
||||
```
|
||||
|
||||
@@ -39,10 +39,10 @@ Windmill uses 6-field cron expressions (includes seconds):
|
||||
|
||||
## CLI Commands
|
||||
|
||||
After writing, tell the user they can run these commands (do NOT run them yourself):
|
||||
`wmill sync push` deploys local changes to the workspace and can be destructive to remote state — only suggest/run it when the user explicitly asks to deploy/publish/push, not when they say "run", "try", or "test". The commands below never mutate remote state, so they're safe to run yourself — note that `sync pull` does overwrite local files to match the remote (use `sync pull --dry-run` to only preview), while `schedule` just lists.
|
||||
|
||||
```bash
|
||||
# Push schedules to Windmill
|
||||
# Push schedules to Windmill — only when the user explicitly asks to deploy
|
||||
wmill sync push
|
||||
|
||||
# Pull schedules from Windmill
|
||||
|
||||
@@ -61,10 +61,10 @@ Text/HTML/inline parts are placed inline in `body` as strings.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
After writing, tell the user they can run these commands (do NOT run them yourself):
|
||||
`wmill sync push` deploys local changes to the workspace and can be destructive to remote state — only suggest/run it when the user explicitly asks to deploy/publish/push, not when they say "run", "try", or "test". `sync pull` is safe to run yourself — it never mutates remote state, though it does overwrite local files to match the remote (use `sync pull --dry-run` to only preview).
|
||||
|
||||
```bash
|
||||
# Push trigger configuration
|
||||
# Push trigger configuration — only when the user explicitly asks to deploy
|
||||
wmill sync push
|
||||
|
||||
# Pull triggers from Windmill
|
||||
|
||||
@@ -237,6 +237,7 @@ wmill resource-type list --schema
|
||||
# Get specific resource type schema
|
||||
wmill resource-type get postgresql
|
||||
|
||||
# Push resources (tell the user to run this, do NOT run it yourself)
|
||||
# Push resources to Windmill — deploys to the workspace and can be destructive to
|
||||
# remote state, so only run it when the user explicitly asks to deploy/publish/push
|
||||
wmill sync push
|
||||
```
|
||||
|
||||
@@ -34,10 +34,10 @@ Windmill uses 6-field cron expressions (includes seconds):
|
||||
|
||||
## CLI Commands
|
||||
|
||||
After writing, tell the user they can run these commands (do NOT run them yourself):
|
||||
`wmill sync push` deploys local changes to the workspace and can be destructive to remote state — only suggest/run it when the user explicitly asks to deploy/publish/push, not when they say "run", "try", or "test". The commands below never mutate remote state, so they're safe to run yourself — note that `sync pull` does overwrite local files to match the remote (use `sync pull --dry-run` to only preview), while `schedule` just lists.
|
||||
|
||||
```bash
|
||||
# Push schedules to Windmill
|
||||
# Push schedules to Windmill — only when the user explicitly asks to deploy
|
||||
wmill sync push
|
||||
|
||||
# Pull schedules from Windmill
|
||||
|
||||
@@ -56,10 +56,10 @@ Text/HTML/inline parts are placed inline in `body` as strings.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
After writing, tell the user they can run these commands (do NOT run them yourself):
|
||||
`wmill sync push` deploys local changes to the workspace and can be destructive to remote state — only suggest/run it when the user explicitly asks to deploy/publish/push, not when they say "run", "try", or "test". `sync pull` is safe to run yourself — it never mutates remote state, though it does overwrite local files to match the remote (use `sync pull --dry-run` to only preview).
|
||||
|
||||
```bash
|
||||
# Push trigger configuration
|
||||
# Push trigger configuration — only when the user explicitly asks to deploy
|
||||
wmill sync push
|
||||
|
||||
# Pull triggers from Windmill
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.720.0",
|
||||
"version": "1.721.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.720.0",
|
||||
"version": "1.721.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"sideEffects": false,
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.720.0
|
||||
1.721.0
|
||||
|
||||
Reference in New Issue
Block a user