Compare commits
14 Commits
dependabot
...
v1.657.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6da492d1b | ||
|
|
87215193ca | ||
|
|
5df37fb0db | ||
|
|
6fa2543647 | ||
|
|
c431053a1e | ||
|
|
a079dd500f | ||
|
|
9d2c439e2a | ||
|
|
fb12b31df0 | ||
|
|
51933be3ca | ||
|
|
404ae09d42 | ||
|
|
e0e78442b7 | ||
|
|
0d31c35f3e | ||
|
|
060687b1fa | ||
|
|
8301d86800 |
2
.github/workflows/backend-test-windows.yml
vendored
2
.github/workflows/backend-test-windows.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
||||
with:
|
||||
bun-version: 1.3.10
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
|
||||
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.10
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- uses: astral-sh/setup-uv@v6.2.1
|
||||
|
||||
6
.github/workflows/cli-tests.yml
vendored
6
.github/workflows/cli-tests.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
cache-workspaces: backend
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
cache-workspaces: backend
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
|
||||
2
.github/workflows/frontend-check.yml
vendored
2
.github/workflows/frontend-check.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubicloud-standard-8
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 24
|
||||
cache: "npm"
|
||||
|
||||
2
.github/workflows/git-sync-test.yml
vendored
2
.github/workflows/git-sync-test.yml
vendored
@@ -129,7 +129,7 @@ jobs:
|
||||
with:
|
||||
deno-version: v2.x
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
|
||||
4
.github/workflows/npm_on_release.yml
vendored
4
.github/workflows/npm_on_release.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubicloud-standard-8
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20.x"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubicloud-standard-8
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20.x"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
2
.github/workflows/validate-openapi.yml
vendored
2
.github/workflows/validate-openapi.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## [1.657.0](https://github.com/windmill-labs/windmill/compare/v1.656.0...v1.657.0) (2026-03-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add datatable config support to CLI settings sync and backend export ([#8024](https://github.com/windmill-labs/windmill/issues/8024)) ([5df37fb](https://github.com/windmill-labs/windmill/commit/5df37fb0dbf9190a430f066cf2d3c48914782e53))
|
||||
|
||||
## [1.656.0](https://github.com/windmill-labs/windmill/compare/v1.655.0...v1.656.0) (2026-03-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add GitHub Enterprise Server (GHES) support for GitHub App git sync ([#8344](https://github.com/windmill-labs/windmill/issues/8344)) ([2e430c4](https://github.com/windmill-labs/windmill/commit/2e430c4c0b8540df7b6997434a7a9f9134858026))
|
||||
* **cli:** add unified generate-metadata command ([#8335](https://github.com/windmill-labs/windmill/issues/8335)) ([4c2c165](https://github.com/windmill-labs/windmill/commit/4c2c165a5b757bd5f2f49074bb290407bce3b2fb))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** add NODE_AUTH_TOKEN for npm publish authentication ([2a8e276](https://github.com/windmill-labs/windmill/commit/2a8e276b6d2761bb2798b6bc5f8d90ab34fbb403))
|
||||
* **ci:** remove provenance flag and use NPM_TOKEN for npm publish ([44dd3ee](https://github.com/windmill-labs/windmill/commit/44dd3ee8cd05d288828d1d46c84cbcdf40f8fa78))
|
||||
* **cli:** exclude raw app backend files from script metadata generation ([#8362](https://github.com/windmill-labs/windmill/issues/8362)) ([060687b](https://github.com/windmill-labs/windmill/commit/060687b1fa6b627a7b06fbdc4b3f4eb0b63411c0))
|
||||
* **cli:** normalize path separators in generate-metadata folder filter for Windows ([#8358](https://github.com/windmill-labs/windmill/issues/8358)) ([404ae09](https://github.com/windmill-labs/windmill/commit/404ae09d429fb545610ba17d747e1903c542d4a3))
|
||||
* **cli:** suppress verbose lock generation messages in generate-metadata ([#8357](https://github.com/windmill-labs/windmill/issues/8357)) ([51933be](https://github.com/windmill-labs/windmill/commit/51933be3cabd853960d384cd358c7bcaef6bfa86))
|
||||
* **frontend:** collapse flow topbar buttons to icon-only in narrow panes ([#8322](https://github.com/windmill-labs/windmill/issues/8322)) ([b585dee](https://github.com/windmill-labs/windmill/commit/b585dee64dfd63d20812ca969b17ff9ee9989493))
|
||||
* **frontend:** filter webhook/email tokens by scope instead of label ([#8363](https://github.com/windmill-labs/windmill/issues/8363)) ([0d31c35](https://github.com/windmill-labs/windmill/commit/0d31c35f3e12d637c757a95fe350294002cbf640))
|
||||
* **frontend:** improve native mode alert message and fix workspaced tag detection ([#8361](https://github.com/windmill-labs/windmill/issues/8361)) ([fb12b31](https://github.com/windmill-labs/windmill/commit/fb12b31df081b2f1ac63becea6e6538ca80f8c46))
|
||||
* **frontend:** prevent duplicate and reserved agent tool names ([#8367](https://github.com/windmill-labs/windmill/issues/8367)) ([c431053](https://github.com/windmill-labs/windmill/commit/c431053a1e24ef29cd551a86de4d013fd7f158be))
|
||||
* graceful shutdown instead of panic on job completion channel failure ([#8345](https://github.com/windmill-labs/windmill/issues/8345)) ([724d135](https://github.com/windmill-labs/windmill/commit/724d1350d070fcf078034a52166d3048fb74e6f3))
|
||||
* Linked resources and vars not triggering both sync jobs on delete ([#8342](https://github.com/windmill-labs/windmill/issues/8342)) ([8e3b8bd](https://github.com/windmill-labs/windmill/commit/8e3b8bdfd2ded9652bc7e876c6bcd0ac2cfae148))
|
||||
* lower default indexer memory/batch settings to prevent OOM ([#8347](https://github.com/windmill-labs/windmill/issues/8347)) ([d9d45cf](https://github.com/windmill-labs/windmill/commit/d9d45cf2f9235b0e7118d0fc97ccdc0776ca9726))
|
||||
|
||||
## [1.655.0](https://github.com/windmill-labs/windmill/compare/v1.654.0...v1.655.0) (2026-03-12)
|
||||
|
||||
|
||||
|
||||
22
CLAUDE.md
22
CLAUDE.md
@@ -4,7 +4,7 @@ Open-source platform for internal tools, workflows, API integrations, background
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Understand**: Before coding, use `wm-ts-nav` to explore (see Code Navigation below). Use `outline` to understand file structure, `body` to read specific symbols, `def`/`callers`/`callees` to trace code. Read `docs/` for domain context.
|
||||
1. **Understand**: Before coding, explore the codebase (see Code Navigation below). Use `outline` to understand file structure, `body` to read specific symbols, `def`/`callers`/`callees` to trace code, `Grep` to find usages. Read `docs/` for domain context.
|
||||
2. **Plan**: For non-trivial changes, use plan mode. For large features, break into reviewable stages
|
||||
3. **Execute**: Follow coding patterns from skills (`rust-backend`, `svelte-frontend`)
|
||||
4. **Validate**: After every change, run the appropriate checks per `docs/validation.md`
|
||||
@@ -52,11 +52,9 @@ let { my_prop = $bindable(default_value) }: { my_prop?: string } = $props()
|
||||
|
||||
## Code Navigation
|
||||
|
||||
`wm-ts-nav` is an AST-aware code navigator. Use **Grep** for regex/pattern search. Use **wm-ts-nav** for structural queries — it skips comments/strings and understands symbol boundaries.
|
||||
`wm-ts-nav` is an AST-aware code navigator. Use **wm-ts-nav** for structural queries — it skips comments/strings and understands symbol boundaries.
|
||||
|
||||
**Prefer wm-ts-nav over Read** to save context window:
|
||||
- `outline <file>` instead of reading a full file — understand structure first, then `body` or Read for specifics
|
||||
- `body "X"` instead of reading a full file to see one function/struct
|
||||
**MUST use `outline` before `Read`** on unfamiliar files — a 500-line file costs ~500 lines of context, while `outline` costs ~20. Then **MUST use `body "X"`** instead of reading a full file to see one function/struct. Use `Read` with offset/limit only when you need surrounding context that `body` doesn't capture.
|
||||
- `refs "X" --caller` instead of reading files to find which function contains each reference
|
||||
- `callers "X"` / `callees "X"` for call-graph questions
|
||||
|
||||
@@ -73,20 +71,14 @@ $NAV --root backend callers "X" # who calls X?
|
||||
$NAV --root backend callees "X" # what does X call?
|
||||
```
|
||||
|
||||
**Limitations** — syntax-level analysis, no type inference:
|
||||
- Import paths are stored literally — `crate::X` and `super::X` pointing to the same type won't be linked
|
||||
- Re-export chains (`pub use`) aren't followed — refs through different re-export paths won't connect
|
||||
- Trait methods can't be resolved to their trait definition
|
||||
- Nested `use` trees (`use foo::{bar::{A, B}, baz::C}`) aren't parsed correctly
|
||||
- Glob imports (`use foo::*`) — refs won't show import origin
|
||||
- Macro-generated symbols (e.g. `sqlx::FromRow`) — invisible to tree-sitter
|
||||
- Single-char identifiers — intentionally filtered out of refs
|
||||
**Limitations** — syntax-level analysis, no type inference. Use **Grep** instead when completeness matters (finding all usages, exhaustiveness checks):
|
||||
- `refs`/`callers`/`callees` can't follow re-exports, glob imports, or different import paths to the same symbol
|
||||
- Trait impls, macro-generated symbols (`sqlx::FromRow`), and namespace member access (`ns.X`) are invisible
|
||||
- `callees` shows all identifiers in a function body, not just actual calls
|
||||
- `import * as ns` namespace imports — member accesses through `ns.X` aren't resolved
|
||||
|
||||
## Core Principles
|
||||
|
||||
- **Use `outline`/`body` to explore, then `Read` with offset/limit from the results before editing** — avoid reading full files
|
||||
- **MUST `outline` before `Read`** on unfamiliar files — then `body` or `Read` with offset/limit for specifics
|
||||
- Search for existing code to reuse before writing new code
|
||||
- Follow established patterns in the codebase
|
||||
- Keep changes focused — don't refactor beyond what's asked
|
||||
|
||||
198
backend/Cargo.lock
generated
198
backend/Cargo.lock
generated
@@ -169,9 +169,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -184,15 +184,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
@@ -1860,9 +1860,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bon"
|
||||
version = "3.9.0"
|
||||
version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d13a61f2963b88eef9c1be03df65d42f6996dfeac1054870d950fcf66686f83"
|
||||
checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe"
|
||||
dependencies = [
|
||||
"bon-macros",
|
||||
"rustversion",
|
||||
@@ -1870,9 +1870,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bon-macros"
|
||||
version = "3.9.0"
|
||||
version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d314cc62af2b6b0c65780555abb4d02a03dd3b799cd42419044f0c38d99738c0"
|
||||
checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c"
|
||||
dependencies = [
|
||||
"darling 0.23.0",
|
||||
"ident_case",
|
||||
@@ -2208,9 +2208,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
version = "1.2.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -2323,9 +2323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.60"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2333,9 +2333,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.60"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -2345,9 +2345,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -2357,9 +2357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
@@ -2418,9 +2418,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
@@ -14697,9 +14697,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
@@ -15741,7 +15741,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -15808,7 +15808,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15821,7 +15821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15962,7 +15962,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15985,7 +15985,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15998,7 +15998,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16024,7 +16024,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -16034,7 +16034,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16051,7 +16051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
@@ -16074,7 +16074,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16097,7 +16097,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16113,7 +16113,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16133,7 +16133,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16153,7 +16153,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16167,7 +16167,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16195,7 +16195,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16220,7 +16220,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"flate2",
|
||||
@@ -16238,7 +16238,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16259,7 +16259,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16279,7 +16279,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16309,7 +16309,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16336,7 +16336,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -16348,7 +16348,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.7.9",
|
||||
@@ -16371,7 +16371,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16385,7 +16385,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16416,7 +16416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -16430,7 +16430,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16449,7 +16449,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -16548,7 +16548,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@@ -16567,7 +16567,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -16582,7 +16582,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -16606,7 +16606,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16623,7 +16623,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -16639,7 +16639,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16660,7 +16660,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16691,7 +16691,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-oauth2",
|
||||
@@ -16715,7 +16715,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -16749,7 +16749,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16767,7 +16767,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -16776,7 +16776,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16788,7 +16788,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16800,7 +16800,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -16812,7 +16812,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16824,7 +16824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16836,7 +16836,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -16847,7 +16847,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16858,7 +16858,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16870,7 +16870,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -16881,7 +16881,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16905,7 +16905,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16919,7 +16919,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -16936,7 +16936,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16950,7 +16950,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -16962,7 +16962,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16980,7 +16980,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -16996,7 +16996,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -17012,7 +17012,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -17023,7 +17023,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17060,7 +17060,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -17098,7 +17098,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"wasm-bindgen",
|
||||
@@ -17109,7 +17109,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17138,7 +17138,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -17161,7 +17161,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17194,7 +17194,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17214,7 +17214,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17248,7 +17248,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17283,7 +17283,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17306,7 +17306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17330,7 +17330,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -17354,7 +17354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17389,7 +17389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17417,7 +17417,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17440,7 +17440,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
@@ -17458,7 +17458,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
@@ -17564,7 +17564,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker-volumes"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -82,7 +82,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -25,6 +25,50 @@ pub mod pydantic_parser;
|
||||
|
||||
const FUNCTION_CALL: &str = "<function call>";
|
||||
|
||||
/// Get the simple type name from an expression (e.g. `str`, `int`).
|
||||
fn simple_type_name(e: &Expr) -> Option<&str> {
|
||||
match e {
|
||||
Expr::Name(ExprName { id, .. }) => Some(id.as_ref()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// If `e` is `list[T]` or `List[T]`, return the inner expression `T`.
|
||||
fn list_elem_expr(e: &Expr) -> Option<&Expr> {
|
||||
match e {
|
||||
Expr::Subscript(x) => match x.value.as_ref() {
|
||||
Expr::Name(ExprName { id, .. }) if id == "list" || id == "List" => {
|
||||
Some(x.slice.as_ref())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect `T | list[T]` or `list[T] | T` union patterns.
|
||||
/// Returns the original type string (e.g. "str | list[str]") for use as `otyp`.
|
||||
fn detect_py_union_array_otyp(e: &Expr) -> Option<String> {
|
||||
let Expr::BinOp(x) = e else { return None };
|
||||
// T | list[T]
|
||||
if let (Some(scalar), Some(elem)) = (simple_type_name(&x.left), list_elem_expr(&x.right)) {
|
||||
if let Some(elem_name) = simple_type_name(elem) {
|
||||
if scalar == elem_name {
|
||||
return Some(format!("{} | list[{}]", scalar, elem_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
// list[T] | T
|
||||
if let (Some(elem), Some(scalar)) = (list_elem_expr(&x.left), simple_type_name(&x.right)) {
|
||||
if let Some(elem_name) = simple_type_name(elem) {
|
||||
if scalar == elem_name {
|
||||
return Some(format!("list[{}] | {}", elem_name, scalar));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Cheap string-based check to see if code might contain Pydantic models or dataclasses.
|
||||
/// Returns true if we should do full AST parsing for type detection, false otherwise.
|
||||
/// This avoids expensive parsing for the common case where scripts don't use these features.
|
||||
@@ -390,8 +434,19 @@ pub fn parse_python_signature(
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Detect T | list[T] union types and set otyp for
|
||||
// debounce accumulation support. Falls back to docstring
|
||||
// description if no union array pattern is found.
|
||||
let union_otyp = params.args[i]
|
||||
.as_arg()
|
||||
.annotation
|
||||
.as_ref()
|
||||
.and_then(|ann| detect_py_union_array_otyp(ann.as_ref()));
|
||||
|
||||
Arg {
|
||||
otyp: metadata.descriptions.get(&arg_name).map(|d| d.to_string()),
|
||||
otyp: union_otyp.or_else(|| {
|
||||
metadata.descriptions.get(&arg_name).map(|d| d.to_string())
|
||||
}),
|
||||
name: arg_name,
|
||||
typ,
|
||||
has_default: has_default || default.is_some(),
|
||||
@@ -441,6 +496,9 @@ fn parse_expr(
|
||||
Expr::Constant(ExprConstant { value: Constant::None, .. })
|
||||
) {
|
||||
(parse_expr(&x.left, enums, module).0, true)
|
||||
} else if detect_py_union_array_otyp(e.as_ref()).is_some() {
|
||||
// T | list[T] — parsed type is Unknown; otyp is set separately
|
||||
(Typ::Unknown, false)
|
||||
} else {
|
||||
(Typ::Unknown, false)
|
||||
}
|
||||
@@ -1046,6 +1104,33 @@ def main(a: str, b: Optional[str], c: str | None): return
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_python_union_array_type() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
def main(items: str | list[str], numbers: list[int] | int, plain: str):
|
||||
pass
|
||||
"#;
|
||||
let result = parse_python_signature(code, None, false)?;
|
||||
assert_eq!(result.args.len(), 3);
|
||||
|
||||
// str | list[str] → otyp set, typ Unknown
|
||||
assert_eq!(result.args[0].name, "items");
|
||||
assert_eq!(result.args[0].otyp, Some("str | list[str]".to_string()));
|
||||
assert_eq!(result.args[0].typ, Typ::Unknown);
|
||||
|
||||
// list[int] | int → otyp set, typ Unknown
|
||||
assert_eq!(result.args[1].name, "numbers");
|
||||
assert_eq!(result.args[1].otyp, Some("list[int] | int".to_string()));
|
||||
assert_eq!(result.args[1].typ, Typ::Unknown);
|
||||
|
||||
// plain str → no otyp
|
||||
assert_eq!(result.args[2].name, "plain");
|
||||
assert_eq!(result.args[2].otyp, None);
|
||||
assert_eq!(result.args[2].typ, Typ::Str(None));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_python_sig_enum() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
|
||||
@@ -363,8 +363,12 @@ fn parse_param(
|
||||
let r = match param.pat {
|
||||
Pat::Ident(ident) => {
|
||||
let (name, typ, nullable) = binding_ident_to_arg(symbol_table, type_resolver, &ident);
|
||||
let otyp = ident
|
||||
.type_ann
|
||||
.as_ref()
|
||||
.and_then(|ta| detect_union_array_otyp(&ta.type_ann));
|
||||
Ok(Arg {
|
||||
otyp: None,
|
||||
otyp,
|
||||
name,
|
||||
typ,
|
||||
default: None,
|
||||
@@ -374,13 +378,21 @@ fn parse_param(
|
||||
}
|
||||
// Pat::Object(ObjectPat { ... }) = todo!()
|
||||
Pat::Assign(AssignPat { left, right, .. }) => {
|
||||
let (name, mut typ, _nullable) = match *left {
|
||||
Pat::Ident(ident) => binding_ident_to_arg(symbol_table, type_resolver, &ident),
|
||||
let (name, mut typ, _nullable, otyp) = match *left {
|
||||
Pat::Ident(ident) => {
|
||||
let otyp = ident
|
||||
.type_ann
|
||||
.as_ref()
|
||||
.and_then(|ta| detect_union_array_otyp(&ta.type_ann));
|
||||
let (name, typ, nullable) =
|
||||
binding_ident_to_arg(symbol_table, type_resolver, &ident);
|
||||
(name, typ, nullable, otyp)
|
||||
}
|
||||
Pat::Object(ObjectPat { type_ann, .. }) => {
|
||||
let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann);
|
||||
*counter += 1;
|
||||
let name = format!("anon{}", counter);
|
||||
(name, typ, nullable)
|
||||
(name, typ, nullable, None)
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -416,7 +428,7 @@ fn parse_param(
|
||||
if typ == Typ::Unknown && dflt.is_some() {
|
||||
typ = json_to_typ(dflt.as_ref().unwrap(), false);
|
||||
}
|
||||
Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true, oidx: None })
|
||||
Ok(Arg { otyp, name, typ, default: dflt, has_default: true, oidx: None })
|
||||
}
|
||||
Pat::Object(ObjectPat { type_ann, .. }) => {
|
||||
let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann);
|
||||
@@ -961,6 +973,75 @@ fn one_of_properties(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn ts_type_to_string(ts_type: &TsType) -> Option<String> {
|
||||
match ts_type {
|
||||
TsType::TsKeywordType(t) => Some(
|
||||
match t.kind {
|
||||
TsKeywordTypeKind::TsStringKeyword => "string",
|
||||
TsKeywordTypeKind::TsNumberKeyword => "number",
|
||||
TsKeywordTypeKind::TsBooleanKeyword => "boolean",
|
||||
TsKeywordTypeKind::TsObjectKeyword => "object",
|
||||
TsKeywordTypeKind::TsBigIntKeyword => "bigint",
|
||||
TsKeywordTypeKind::TsAnyKeyword => "any",
|
||||
_ => return None,
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
TsType::TsTypeRef(TsTypeRef { type_name, .. }) => match type_name {
|
||||
TsEntityName::Ident(Ident { sym, .. }) => Some(sym.to_string()),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_array_elem_type(ts_type: &TsType) -> Option<&TsType> {
|
||||
match ts_type {
|
||||
TsType::TsArrayType(TsArrayType { elem_type, .. }) => Some(elem_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects union types of the form `T | T[]` or `T[] | T` and returns
|
||||
/// the original type string (e.g. "string | string[]").
|
||||
fn detect_union_array_otyp(ts_type: &TsType) -> Option<String> {
|
||||
let TsType::TsUnionOrIntersectionType(TsUnionOrIntersectionType::TsUnionType(TsUnionType {
|
||||
types,
|
||||
..
|
||||
})) = ts_type
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if types.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check pattern: T | T[]
|
||||
if let (Some(scalar_name), Some(array_elem)) =
|
||||
(ts_type_to_string(&types[0]), get_array_elem_type(&types[1]))
|
||||
{
|
||||
if let Some(elem_name) = ts_type_to_string(array_elem) {
|
||||
if scalar_name == elem_name {
|
||||
return Some(format!("{} | {}[]", scalar_name, elem_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check pattern: T[] | T
|
||||
if let (Some(array_elem), Some(scalar_name)) =
|
||||
(get_array_elem_type(&types[0]), ts_type_to_string(&types[1]))
|
||||
{
|
||||
if let Some(elem_name) = ts_type_to_string(array_elem) {
|
||||
if scalar_name == elem_name {
|
||||
return Some(format!("{}[] | {}", elem_name, scalar_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn find_undefined(types: &Vec<Box<TsType>>) -> Option<usize> {
|
||||
types.into_iter().position(|x| match **x {
|
||||
TsType::TsKeywordType(TsKeywordType { kind, .. }) => {
|
||||
|
||||
@@ -646,6 +646,55 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_union_array_type() {
|
||||
let code = r#"
|
||||
export async function main(
|
||||
items: string | string[],
|
||||
numbers: number[] | number,
|
||||
plain: string
|
||||
) {
|
||||
return { items, numbers, plain };
|
||||
}
|
||||
"#;
|
||||
let sig = parse_deno_signature(code, false, false, None).unwrap();
|
||||
assert_eq!(
|
||||
sig,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: vec![
|
||||
Arg {
|
||||
name: "items".to_string(),
|
||||
otyp: Some("string | string[]".to_string()),
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
name: "numbers".to_string(),
|
||||
otyp: Some("number[] | number".to_string()),
|
||||
typ: Typ::Unknown,
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
Arg {
|
||||
name: "plain".to_string(),
|
||||
otyp: None,
|
||||
typ: Typ::Str(None),
|
||||
default: None,
|
||||
has_default: false,
|
||||
oidx: None,
|
||||
},
|
||||
],
|
||||
no_main_func: Some(false),
|
||||
has_preprocessor: Some(false),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_typescript() {
|
||||
let code = r#"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.655.0
|
||||
version: 1.657.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -283,6 +283,8 @@ struct SimplifiedSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
operator_settings: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
datatable: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
slack_team_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
slack_name: Option<String>,
|
||||
@@ -323,6 +325,8 @@ struct SimplifiedSettingsLegacy {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
operator_settings: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
datatable: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
slack_team_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
slack_name: Option<String>,
|
||||
@@ -347,6 +351,7 @@ struct SettingsRow {
|
||||
mute_critical_alerts: Option<bool>,
|
||||
color: Option<String>,
|
||||
operator_settings: Option<serde_json::Value>,
|
||||
datatable: Option<Value>,
|
||||
slack_team_id: Option<String>,
|
||||
slack_name: Option<String>,
|
||||
slack_command_script: Option<String>,
|
||||
@@ -955,6 +960,7 @@ pub(crate) async fn tarball_workspace(
|
||||
mute_critical_alerts,
|
||||
color,
|
||||
operator_settings,
|
||||
datatable,
|
||||
slack_team_id,
|
||||
slack_name,
|
||||
slack_command_script
|
||||
@@ -983,6 +989,7 @@ pub(crate) async fn tarball_workspace(
|
||||
mute_critical_alerts: row.mute_critical_alerts,
|
||||
color: row.color.clone(),
|
||||
operator_settings: row.operator_settings.clone(),
|
||||
datatable: row.datatable.clone(),
|
||||
slack_team_id: row.slack_team_id.clone(),
|
||||
slack_name: row.slack_name.clone(),
|
||||
slack_command_script: row.slack_command_script.clone(),
|
||||
@@ -1045,6 +1052,7 @@ pub(crate) async fn tarball_workspace(
|
||||
mute_critical_alerts: row.mute_critical_alerts,
|
||||
color: row.color,
|
||||
operator_settings: row.operator_settings,
|
||||
datatable: row.datatable,
|
||||
slack_team_id: row.slack_team_id,
|
||||
slack_name: row.slack_name,
|
||||
slack_command_script: row.slack_command_script,
|
||||
|
||||
@@ -3030,8 +3030,16 @@ impl PulledJobResult {
|
||||
if let Some(s) = str_o.as_ref() {
|
||||
match serde_json::from_str::<Vec<Box<RawValue>>>(s) {
|
||||
Ok(ref mut vec) => accumulated_arg.append(vec),
|
||||
Err(e) => {
|
||||
return Err(error::Error::ArgumentErr(format!("cannot consolidate arguments of non-list type. Type provided for argument `{arg_name_to_accumulate}` is not a list\nUnwrapped Error: {e}")));
|
||||
Err(_) => {
|
||||
// Value is not an array — wrap the scalar into a
|
||||
// single-element array. This supports union types
|
||||
// like T | T[] where the caller may pass a bare T.
|
||||
match RawValue::from_string(s.to_string()) {
|
||||
Ok(raw) => accumulated_arg.push(raw),
|
||||
Err(e) => {
|
||||
return Err(error::Error::ArgumentErr(format!("cannot consolidate argument `{arg_name_to_accumulate}`: value is neither a valid list nor a valid JSON value\nUnwrapped Error: {e}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.655.0";
|
||||
export const VERSION = "v1.657.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
@@ -93,7 +93,16 @@ async function generateAppHash(
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates locks for inline scripts in an app
|
||||
* Result of generating app locks, including which scripts were updated
|
||||
*/
|
||||
export interface AppLocksResult {
|
||||
path: string;
|
||||
updatedScripts: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates locks for inline scripts in an app.
|
||||
* Returns the path if dry-run, or AppLocksResult with updated scripts if actual update occurred.
|
||||
*/
|
||||
export async function generateAppLocksInternal(
|
||||
appFolder: string,
|
||||
@@ -105,7 +114,7 @@ export async function generateAppLocksInternal(
|
||||
},
|
||||
justUpdateMetadataLock?: boolean,
|
||||
noStaleMessage?: boolean
|
||||
): Promise<string | void> {
|
||||
): Promise<string | AppLocksResult | void> {
|
||||
if (appFolder.endsWith(SEP)) {
|
||||
appFolder = appFolder.substring(0, appFolder.length - 1);
|
||||
}
|
||||
@@ -167,6 +176,8 @@ export async function generateAppLocksInternal(
|
||||
);
|
||||
}
|
||||
|
||||
let updatedScripts: string[] = [];
|
||||
|
||||
if (!justUpdateMetadataLock) {
|
||||
const changedScripts = [];
|
||||
// Find hashes that do not correspond to previous hashes
|
||||
@@ -201,13 +212,14 @@ export async function generateAppLocksInternal(
|
||||
replaceInlineScripts(runnables, runnablesPath + SEP, false);
|
||||
|
||||
// Update the app runnables with new locks (writes to separate files)
|
||||
await updateRawAppRunnables(
|
||||
updatedScripts = await updateRawAppRunnables(
|
||||
workspace,
|
||||
runnables,
|
||||
remote_path,
|
||||
appFolder,
|
||||
filteredDeps,
|
||||
opts.defaultTs
|
||||
opts.defaultTs,
|
||||
noStaleMessage
|
||||
);
|
||||
// Note: updateRawAppRunnables now writes each runnable to its own file
|
||||
} else {
|
||||
@@ -217,14 +229,17 @@ export async function generateAppLocksInternal(
|
||||
replaceInlineScripts(normalAppFile.value, appFolder + SEP, false);
|
||||
|
||||
// Update the app value with new locks
|
||||
normalAppFile.value = await updateAppInlineScripts(
|
||||
const result = await updateAppInlineScripts(
|
||||
workspace,
|
||||
normalAppFile.value,
|
||||
remote_path,
|
||||
appFolder,
|
||||
filteredDeps,
|
||||
opts.defaultTs
|
||||
opts.defaultTs,
|
||||
noStaleMessage
|
||||
);
|
||||
normalAppFile.value = result.value;
|
||||
updatedScripts = result.updatedScripts;
|
||||
|
||||
// Write the updated app file (only for normal apps, raw apps use separate files)
|
||||
writeIfChanged(
|
||||
@@ -251,6 +266,8 @@ export async function generateAppLocksInternal(
|
||||
if (!noStaleMessage) {
|
||||
log.info(colors.green(`App ${remote_path} lockfiles updated`));
|
||||
}
|
||||
|
||||
return { path: remote_path, updatedScripts };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,6 +357,7 @@ async function traverseAndProcessInlineScripts(
|
||||
* Updates locks for all runnables in a raw app, generating locks inline script by inline script.
|
||||
* Writes each runnable to its own YAML file in the backend folder (new format).
|
||||
* Also writes content and lock files to the runnables folder.
|
||||
* Returns the list of runnable IDs that had their locks updated.
|
||||
*/
|
||||
async function updateRawAppRunnables(
|
||||
workspace: Workspace,
|
||||
@@ -347,8 +365,10 @@ async function updateRawAppRunnables(
|
||||
remotePath: string,
|
||||
appFolder: string,
|
||||
rawDeps?: Record<string, string>,
|
||||
defaultTs: "bun" | "deno" = "bun"
|
||||
): Promise<void> {
|
||||
defaultTs: "bun" | "deno" = "bun",
|
||||
noStaleMessage?: boolean
|
||||
): Promise<string[]> {
|
||||
const updatedRunnables: string[] = [];
|
||||
const runnablesFolder = path.join(appFolder, APP_BACKEND_FOLDER);
|
||||
|
||||
// Ensure runnables folder exists
|
||||
@@ -414,12 +434,11 @@ async function updateRawAppRunnables(
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info(
|
||||
colors.gray(
|
||||
`Generating lock for runnable ${runnableId} (${language})
|
||||
}`
|
||||
)
|
||||
);
|
||||
if (!noStaleMessage) {
|
||||
log.info(
|
||||
colors.gray(`Generating lock for runnable ${runnableId} (${language})`)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const lock = await generateInlineScriptLock(
|
||||
@@ -459,11 +478,15 @@ async function updateRawAppRunnables(
|
||||
// Write the runnable to its own YAML file
|
||||
writeRunnableToBackend(runnablesFolder, runnableId, simplifiedRunnable);
|
||||
|
||||
log.info(
|
||||
colors.gray(
|
||||
` Written ${runnableId}.yaml, ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}`
|
||||
)
|
||||
);
|
||||
updatedRunnables.push(runnableId);
|
||||
|
||||
if (!noStaleMessage) {
|
||||
log.info(
|
||||
colors.gray(
|
||||
` Written ${runnableId}.yaml, ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}`
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(
|
||||
colors.red(
|
||||
@@ -474,11 +497,14 @@ async function updateRawAppRunnables(
|
||||
writeRunnableToBackend(runnablesFolder, runnableId, runnable);
|
||||
}
|
||||
}
|
||||
|
||||
return updatedRunnables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates locks for all inline scripts in a normal app, similar to updateRawAppRunnables
|
||||
* but for the app.value structure instead of app.runnables
|
||||
* but for the app.value structure instead of app.runnables.
|
||||
* Returns a tuple of [updated app value, list of script names that were updated].
|
||||
*/
|
||||
async function updateAppInlineScripts(
|
||||
workspace: Workspace,
|
||||
@@ -486,9 +512,11 @@ async function updateAppInlineScripts(
|
||||
remotePath: string,
|
||||
appFolder: string,
|
||||
rawDeps?: Record<string, string>,
|
||||
defaultTs: "bun" | "deno" = "bun"
|
||||
): Promise<any> {
|
||||
defaultTs: "bun" | "deno" = "bun",
|
||||
noStaleMessage?: boolean
|
||||
): Promise<{ value: any; updatedScripts: string[] }> {
|
||||
const pathAssigner = newPathAssigner(defaultTs, { skipInlineScriptSuffix: getNonDottedPaths() });
|
||||
const updatedScripts: string[] = [];
|
||||
|
||||
const processor: InlineScriptProcessor = async (inlineScript, context) => {
|
||||
const language = inlineScript.language as SupportedLanguage;
|
||||
@@ -518,13 +546,15 @@ async function updateAppInlineScripts(
|
||||
try {
|
||||
let lock: string | undefined;
|
||||
if (language !== "frontend") {
|
||||
log.info(
|
||||
colors.gray(
|
||||
`Generating lock for inline script "${scriptName}" at ${context.path.join(
|
||||
"."
|
||||
)} (${language})`
|
||||
)
|
||||
);
|
||||
if (!noStaleMessage) {
|
||||
log.info(
|
||||
colors.gray(
|
||||
`Generating lock for inline script "${scriptName}" at ${context.path.join(
|
||||
"."
|
||||
)} (${language})`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
lock = await generateInlineScriptLock(
|
||||
workspace,
|
||||
@@ -553,11 +583,18 @@ async function updateAppInlineScripts(
|
||||
const inlineLockRef =
|
||||
lock && lock !== "" ? `!inline ${basePath}lock` : "";
|
||||
|
||||
log.info(
|
||||
colors.gray(
|
||||
` Written ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}`
|
||||
)
|
||||
);
|
||||
if (!noStaleMessage) {
|
||||
log.info(
|
||||
colors.gray(
|
||||
` Written ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Track that this script was updated (only for non-frontend scripts that needed locks)
|
||||
if (language !== "frontend") {
|
||||
updatedScripts.push(scriptName);
|
||||
}
|
||||
|
||||
return {
|
||||
...inlineScript,
|
||||
@@ -577,7 +614,8 @@ async function updateAppInlineScripts(
|
||||
}
|
||||
};
|
||||
|
||||
return await traverseAndProcessInlineScripts(appValue, processor);
|
||||
const updatedValue = await traverseAndProcessInlineScripts(appValue, processor);
|
||||
return { value: updatedValue, updatedScripts };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,6 +51,14 @@ async function generateFlowHash(
|
||||
}
|
||||
return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) };
|
||||
}
|
||||
/**
|
||||
* Result of generating flow locks, including which scripts were updated
|
||||
*/
|
||||
export interface FlowLocksResult {
|
||||
path: string;
|
||||
updatedScripts: string[];
|
||||
}
|
||||
|
||||
export async function generateFlowLockInternal(
|
||||
folder: string,
|
||||
dryRun: boolean,
|
||||
@@ -60,7 +68,7 @@ export async function generateFlowLockInternal(
|
||||
},
|
||||
justUpdateMetadataLock?: boolean,
|
||||
noStaleMessage?: boolean
|
||||
): Promise<string | void> {
|
||||
): Promise<string | FlowLocksResult | void> {
|
||||
if (folder.endsWith(SEP)) {
|
||||
folder = folder.substring(0, folder.length - 1);
|
||||
}
|
||||
@@ -109,8 +117,9 @@ export async function generateFlowLockInternal(
|
||||
}
|
||||
|
||||
|
||||
let changedScripts: string[] = [];
|
||||
|
||||
if (!justUpdateMetadataLock) {
|
||||
const changedScripts = [];
|
||||
//find hashes that do not correspond to previous hashes
|
||||
for (const [path, hash] of Object.entries(hashes)) {
|
||||
if (path == TOP_HASH) {
|
||||
@@ -185,6 +194,13 @@ export async function generateFlowLockInternal(
|
||||
if (!noStaleMessage) {
|
||||
log.info(colors.green(`Flow ${remote_path} lockfiles updated`));
|
||||
}
|
||||
|
||||
// Return the list of updated scripts (extract just the filename from the path)
|
||||
const updatedScripts = changedScripts.map(p => {
|
||||
const parts = p.split(SEP);
|
||||
return parts[parts.length - 1].replace(/\.[^.]+$/, ""); // Remove extension
|
||||
});
|
||||
return { path: remote_path, updatedScripts };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,15 +11,15 @@ import {
|
||||
generateScriptMetadataInternal,
|
||||
getRawWorkspaceDependencies,
|
||||
} from "../../utils/metadata.ts";
|
||||
import { generateFlowLockInternal } from "../flow/flow_metadata.ts";
|
||||
import { generateAppLocksInternal, getAppFolders } from "../app/app_metadata.ts";
|
||||
import { generateFlowLockInternal, FlowLocksResult } from "../flow/flow_metadata.ts";
|
||||
import { generateAppLocksInternal, getAppFolders, AppLocksResult } from "../app/app_metadata.ts";
|
||||
import {
|
||||
elementsToMap,
|
||||
FSFSElement,
|
||||
ignoreF,
|
||||
} from "../sync/sync.ts";
|
||||
import { exts } from "../script/script.ts";
|
||||
import { isFlowPath, isAppPath } from "../../utils/resource_folders.ts";
|
||||
import { isFlowPath, isAppPath, isRawAppPath } from "../../utils/resource_folders.ts";
|
||||
import { listSyncCodebases } from "../../utils/codebase.ts";
|
||||
|
||||
interface StaleItem {
|
||||
@@ -82,7 +82,8 @@ async function generateMetadata(
|
||||
(!isD && !exts.some((ext) => p.endsWith(ext))) ||
|
||||
ignore(p, isD) ||
|
||||
isFlowPath(p) ||
|
||||
isAppPath(p)
|
||||
isAppPath(p) ||
|
||||
isRawAppPath(p)
|
||||
);
|
||||
},
|
||||
false,
|
||||
@@ -192,12 +193,17 @@ async function generateMetadata(
|
||||
// === Filter by folder if specified ===
|
||||
let filteredItems = staleItems;
|
||||
if (folder) {
|
||||
// Strip trailing separator to match deprecated flow/app handler behavior
|
||||
// (see generateFlowLockInternal line 64-66, generateAppLocksInternal line 109-110)
|
||||
if (folder.endsWith(SEP)) {
|
||||
// Normalize to forward slashes (Windows users may use backslashes)
|
||||
folder = folder.replaceAll("\\", "/");
|
||||
// Strip trailing slash to match deprecated flow/app handler behavior
|
||||
if (folder.endsWith("/")) {
|
||||
folder = folder.substring(0, folder.length - 1);
|
||||
}
|
||||
filteredItems = staleItems.filter((item) => item.folder === folder || item.folder.startsWith(folder + SEP));
|
||||
// Normalize item.folder for comparison (Windows file paths use backslashes)
|
||||
filteredItems = staleItems.filter((item) => {
|
||||
const normalizedFolder = item.folder.replaceAll("\\", "/");
|
||||
return normalizedFolder === folder || normalizedFolder.startsWith(folder + "/");
|
||||
});
|
||||
}
|
||||
|
||||
// === Show stale items and confirm ===
|
||||
@@ -280,21 +286,23 @@ async function generateMetadata(
|
||||
// Process flows
|
||||
for (const item of flows) {
|
||||
current++;
|
||||
log.info(`${formatProgress(current)} flow ${colors.cyan(item.path)}`);
|
||||
await generateFlowLockInternal(
|
||||
const result = await generateFlowLockInternal(
|
||||
item.folder,
|
||||
false, // dryRun
|
||||
workspace,
|
||||
opts,
|
||||
false,
|
||||
true // noStaleMessage - we handle output
|
||||
);
|
||||
) as FlowLocksResult | void;
|
||||
const scriptsInfo = result?.updatedScripts?.length
|
||||
? `: ${colors.gray(result.updatedScripts.join(", "))}`
|
||||
: "";
|
||||
log.info(`${formatProgress(current)} flow ${colors.cyan(item.path)}${scriptsInfo}`);
|
||||
}
|
||||
// Process apps
|
||||
for (const item of apps) {
|
||||
current++;
|
||||
log.info(`${formatProgress(current)} app ${colors.cyan(item.path)}`);
|
||||
await generateAppLocksInternal(
|
||||
const result = await generateAppLocksInternal(
|
||||
item.folder,
|
||||
item.isRawApp!, // rawApp
|
||||
false, // dryRun
|
||||
@@ -302,7 +310,11 @@ async function generateMetadata(
|
||||
opts,
|
||||
false,
|
||||
true // noStaleMessage - we handle output
|
||||
);
|
||||
) as AppLocksResult | void;
|
||||
const scriptsInfo = result?.updatedScripts?.length
|
||||
? `: ${colors.gray(result.updatedScripts.join(", "))}`
|
||||
: "";
|
||||
log.info(`${formatProgress(current)} app ${colors.cyan(item.path)}${scriptsInfo}`);
|
||||
}
|
||||
|
||||
log.info("");
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
isFlowInlineScriptPath as isFlowInlineScriptPathInternal,
|
||||
isFlowPath,
|
||||
isAppPath,
|
||||
isRawAppPath,
|
||||
} from "../../utils/resource_folders.ts";
|
||||
|
||||
export interface ScriptFile {
|
||||
@@ -1027,7 +1028,8 @@ export async function generateMetadata(
|
||||
(!isD && !exts.some((ext) => p.endsWith(ext))) ||
|
||||
ignore(p, isD) ||
|
||||
isFlowPath(p) ||
|
||||
isAppPath(p)
|
||||
isAppPath(p) ||
|
||||
isRawAppPath(p)
|
||||
);
|
||||
},
|
||||
false,
|
||||
|
||||
@@ -53,6 +53,7 @@ export interface SimplifiedSettings {
|
||||
mute_critical_alerts?: boolean;
|
||||
color?: string;
|
||||
operator_settings?: any;
|
||||
datatable?: any;
|
||||
slack_team_id?: string;
|
||||
slack_name?: string;
|
||||
slack_command_script?: string;
|
||||
@@ -100,6 +101,7 @@ export function migrateToGroupedFormat(settings: any): SimplifiedSettings {
|
||||
if (settings.mute_critical_alerts !== undefined) result.mute_critical_alerts = settings.mute_critical_alerts;
|
||||
if (settings.color !== undefined) result.color = settings.color;
|
||||
if (settings.operator_settings !== undefined) result.operator_settings = settings.operator_settings;
|
||||
if (settings.datatable !== undefined) result.datatable = settings.datatable;
|
||||
if (settings.slack_team_id !== undefined) result.slack_team_id = settings.slack_team_id;
|
||||
if (settings.slack_name !== undefined) result.slack_name = settings.slack_name;
|
||||
if (settings.slack_command_script !== undefined) result.slack_command_script = settings.slack_command_script;
|
||||
@@ -192,6 +194,7 @@ export async function pushWorkspaceSettings(
|
||||
mute_critical_alerts: remoteSettings.mute_critical_alerts,
|
||||
color: remoteSettings.color,
|
||||
operator_settings: remoteSettings.operator_settings,
|
||||
datatable: remoteSettings.datatable,
|
||||
slack_team_id: remoteSettings.slack_team_id,
|
||||
slack_name: remoteSettings.slack_name,
|
||||
slack_command_script: remoteSettings.slack_command_script,
|
||||
@@ -382,6 +385,14 @@ export async function pushWorkspaceSettings(
|
||||
});
|
||||
}
|
||||
|
||||
if (!deepEqual(localSettings.datatable, settings.datatable)) {
|
||||
log.debug(`Updating datatable config...`);
|
||||
await wmill.editDataTableConfig({
|
||||
workspace,
|
||||
requestBody: { settings: localSettings.datatable ?? { datatables: {} } },
|
||||
});
|
||||
}
|
||||
|
||||
if (localSettings.slack_command_script != settings.slack_command_script) {
|
||||
log.debug(`Updating slack command script...`);
|
||||
await wmill.editSlackCommand({
|
||||
|
||||
@@ -5072,6 +5072,23 @@ folder related commands
|
||||
- \`folder add-missing\` - create default folder.meta.yaml for all subdirectories of f/ that are missing one
|
||||
- \`-y, --yes\` - skip confirmation prompt
|
||||
|
||||
### generate-metadata
|
||||
|
||||
Generate metadata (locks, schemas) for all scripts, flows, and apps
|
||||
|
||||
**Arguments:** \`[folder:string]\`
|
||||
|
||||
**Options:**
|
||||
- \`--yes\` - Skip confirmation prompt
|
||||
- \`--dry-run\` - Show what would be updated without making changes
|
||||
- \`--lock-only\` - Re-generate only the lock files
|
||||
- \`--schema-only\` - Re-generate only script schemas (skips flows and apps)
|
||||
- \`--skip-scripts\` - Skip processing scripts
|
||||
- \`--skip-flows\` - Skip processing flows
|
||||
- \`--skip-apps\` - Skip processing apps
|
||||
- \`-i --includes <patterns:file[]>\` - Comma separated patterns to specify which files to include
|
||||
- \`-e --excludes <patterns:file[]>\` - Comma separated patterns to specify which files to exclude
|
||||
|
||||
### gitsync-settings
|
||||
|
||||
Manage git-sync settings between local wmill.yaml and Windmill backend
|
||||
|
||||
@@ -68,7 +68,7 @@ export {
|
||||
workspaceAdd,
|
||||
};
|
||||
|
||||
export const VERSION = "1.655.0";
|
||||
export const VERSION = "1.657.0";
|
||||
|
||||
// Re-exported from constants.ts to maintain backwards compatibility
|
||||
export { WM_FORK_PREFIX } from "./core/constants.ts";
|
||||
|
||||
@@ -50,26 +50,25 @@ export class LockfileGenerationError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateAllMetadata() {}
|
||||
|
||||
export async function getRawWorkspaceDependencies(): Promise<Record<string, string>> {
|
||||
const rawWorkspaceDeps: Record<string, string> = {};
|
||||
|
||||
|
||||
try {
|
||||
const entries = await readdir("dependencies", { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) continue;
|
||||
|
||||
|
||||
const filePath = `dependencies/${entry.name}`;
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
|
||||
|
||||
// Find matching language
|
||||
for (const lang of workspaceDependenciesLanguages) {
|
||||
if (entry.name.endsWith(lang.filename)) {
|
||||
// Check if out of sync
|
||||
const contentHash = await generateHash(content + filePath);
|
||||
const isUpToDate = await checkifMetadataUptodate(filePath, contentHash, undefined);
|
||||
|
||||
|
||||
if (!isUpToDate) {
|
||||
rawWorkspaceDeps[filePath] = content;
|
||||
}
|
||||
@@ -691,6 +690,11 @@ export async function inferSchema(
|
||||
|
||||
argSigToJsonSchemaType(arg.typ, currentSchema.properties[arg.name]);
|
||||
|
||||
// For T | T[] detection for debouncing arg accumulation
|
||||
if ((arg as any).otyp && (arg as any).otyp.includes('[') && (arg as any).otyp.includes('|')) {
|
||||
currentSchema.properties[arg.name].originalType = (arg as any).otyp
|
||||
}
|
||||
|
||||
currentSchema.properties[arg.name].default = arg.default;
|
||||
|
||||
if (!arg.has_default && !currentSchema.required.includes(arg.name)) {
|
||||
|
||||
226
cli/test/datatable_settings_sync.test.ts
Normal file
226
cli/test/datatable_settings_sync.test.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Datatable settings sync tests
|
||||
*
|
||||
* Tests that datatable config is correctly synced via settings.yaml during pull/push operations.
|
||||
*/
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import { writeFile, readFile } from "node:fs/promises";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { withTestBackend } from "./test_backend.ts";
|
||||
import { shouldSkipOnCI } from "./cargo_backend.ts";
|
||||
import { addWorkspace } from "../workspace.ts";
|
||||
|
||||
test.skipIf(shouldSkipOnCI())("Datatable config: included in sync pull settings.yaml", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
const testWorkspace = {
|
||||
remote: backend.baseUrl,
|
||||
workspaceId: backend.workspace,
|
||||
name: "datatable_pull_test",
|
||||
token: backend.token
|
||||
};
|
||||
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
|
||||
|
||||
// Configure datatable on backend
|
||||
const datatableConfig = {
|
||||
datatables: {
|
||||
main: {
|
||||
database: {
|
||||
resource_path: "u/test/test_db",
|
||||
resource_type: "postgresql"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const configResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/workspaces/edit_datatable_config`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ settings: datatableConfig })
|
||||
}
|
||||
);
|
||||
expect(configResp.ok).toBe(true);
|
||||
|
||||
// Create wmill.yaml with includeSettings
|
||||
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
includeSettings: true`, "utf-8");
|
||||
|
||||
// Pull settings
|
||||
const result = await backend.runCLICommand(['sync', 'pull', '--yes'], tempDir);
|
||||
expect(result.code).toEqual(0);
|
||||
|
||||
// Verify settings.yaml was created and contains datatable
|
||||
const settingsContent = await readFile(`${tempDir}/settings.yaml`, "utf-8");
|
||||
expect(settingsContent).toContain("datatable:");
|
||||
expect(settingsContent).toContain("u/test/test_db");
|
||||
expect(settingsContent).toContain("postgresql");
|
||||
});
|
||||
});
|
||||
|
||||
test.skipIf(shouldSkipOnCI())("Datatable config: pushed correctly from settings.yaml", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
const testWorkspace = {
|
||||
remote: backend.baseUrl,
|
||||
workspaceId: backend.workspace,
|
||||
name: "datatable_push_test",
|
||||
token: backend.token
|
||||
};
|
||||
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
|
||||
|
||||
// Create wmill.yaml with includeSettings
|
||||
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
includeSettings: true`, "utf-8");
|
||||
|
||||
// First pull to get baseline settings
|
||||
const pullResult = await backend.runCLICommand(['sync', 'pull', '--yes'], tempDir);
|
||||
expect(pullResult.code).toEqual(0);
|
||||
|
||||
// Read existing settings and add datatable config
|
||||
const settingsContent = await readFile(`${tempDir}/settings.yaml`, "utf-8");
|
||||
const existingSettings = parse(settingsContent) as Record<string, unknown>;
|
||||
|
||||
existingSettings.datatable = {
|
||||
datatables: {
|
||||
analytics: {
|
||||
database: {
|
||||
resource_path: "u/admin/analytics_db",
|
||||
resource_type: "postgresql"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await writeFile(`${tempDir}/settings.yaml`, stringify(existingSettings), "utf-8");
|
||||
|
||||
// Push the modified settings
|
||||
const pushResult = await backend.runCLICommand(['sync', 'push', '--yes'], tempDir);
|
||||
expect(pushResult.code).toEqual(0);
|
||||
|
||||
// Verify the backend has the updated datatable config
|
||||
const settingsResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/workspaces/get_settings`
|
||||
);
|
||||
expect(settingsResp.ok).toBe(true);
|
||||
const settings = await settingsResp.json();
|
||||
|
||||
expect(settings.datatable).toBeDefined();
|
||||
expect(settings.datatable.datatables).toBeDefined();
|
||||
expect(settings.datatable.datatables.analytics).toBeDefined();
|
||||
expect(settings.datatable.datatables.analytics.database.resource_path).toEqual("u/admin/analytics_db");
|
||||
expect(settings.datatable.datatables.analytics.database.resource_type).toEqual("postgresql");
|
||||
});
|
||||
});
|
||||
|
||||
test.skipIf(shouldSkipOnCI())("Datatable config: empty/undefined doesn't cause errors", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
const testWorkspace = {
|
||||
remote: backend.baseUrl,
|
||||
workspaceId: backend.workspace,
|
||||
name: "datatable_empty_test",
|
||||
token: backend.token
|
||||
};
|
||||
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
|
||||
|
||||
// Ensure datatable config is empty/cleared on backend
|
||||
const clearResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/workspaces/edit_datatable_config`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ settings: { datatables: {} } })
|
||||
}
|
||||
);
|
||||
expect(clearResp.ok).toBe(true);
|
||||
|
||||
// Create wmill.yaml with includeSettings
|
||||
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
includeSettings: true`, "utf-8");
|
||||
|
||||
// Pull should succeed even with empty datatable config
|
||||
const pullResult = await backend.runCLICommand(['sync', 'pull', '--yes'], tempDir);
|
||||
expect(pullResult.code).toEqual(0);
|
||||
|
||||
// Settings.yaml should exist
|
||||
const settingsContent = await readFile(`${tempDir}/settings.yaml`, "utf-8");
|
||||
expect(settingsContent.length).toBeGreaterThan(0);
|
||||
|
||||
// Push should also succeed
|
||||
const pushResult = await backend.runCLICommand(['sync', 'push', '--yes'], tempDir);
|
||||
expect(pushResult.code).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.skipIf(shouldSkipOnCI())("Datatable config: round-trip preserves structure", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
const testWorkspace = {
|
||||
remote: backend.baseUrl,
|
||||
workspaceId: backend.workspace,
|
||||
name: "datatable_roundtrip_test",
|
||||
token: backend.token
|
||||
};
|
||||
await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir });
|
||||
|
||||
// Set up a complex datatable config on backend with multiple datatables
|
||||
const originalConfig = {
|
||||
datatables: {
|
||||
users: {
|
||||
database: {
|
||||
resource_path: "f/shared/users_db",
|
||||
resource_type: "postgresql"
|
||||
}
|
||||
},
|
||||
logs: {
|
||||
database: {
|
||||
resource_path: "f/shared/logs_db",
|
||||
resource_type: "instance"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const configResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/workspaces/edit_datatable_config`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ settings: originalConfig })
|
||||
}
|
||||
);
|
||||
expect(configResp.ok).toBe(true);
|
||||
|
||||
// Create wmill.yaml with includeSettings
|
||||
await writeFile(`${tempDir}/wmill.yaml`, `defaultTs: bun
|
||||
includes:
|
||||
- "**"
|
||||
includeSettings: true`, "utf-8");
|
||||
|
||||
// Pull
|
||||
const pullResult = await backend.runCLICommand(['sync', 'pull', '--yes'], tempDir);
|
||||
expect(pullResult.code).toEqual(0);
|
||||
|
||||
// Push back without modification
|
||||
const pushResult = await backend.runCLICommand(['sync', 'push', '--yes'], tempDir);
|
||||
expect(pushResult.code).toEqual(0);
|
||||
|
||||
// Verify the config is preserved
|
||||
const settingsResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/workspaces/get_settings`
|
||||
);
|
||||
expect(settingsResp.ok).toBe(true);
|
||||
const settings = await settingsResp.json();
|
||||
|
||||
expect(settings.datatable).toBeDefined();
|
||||
expect(settings.datatable.datatables.users).toBeDefined();
|
||||
expect(settings.datatable.datatables.logs).toBeDefined();
|
||||
expect(settings.datatable.datatables.users.database.resource_path).toEqual("f/shared/users_db");
|
||||
expect(settings.datatable.datatables.logs.database.resource_type).toEqual("instance");
|
||||
});
|
||||
});
|
||||
@@ -107,7 +107,7 @@ async function readFileContent(filePath: string): Promise<string> {
|
||||
* Create a raw app directory structure on disk
|
||||
* Uses .raw_app folder suffix with raw_app.yaml metadata
|
||||
*/
|
||||
async function createRawAppOnDisk(appDir: string): Promise<void> {
|
||||
async function createRawAppOnDisk(appDir: string, includeBackend: boolean = false): Promise<void> {
|
||||
await mkdir(appDir, { recursive: true });
|
||||
await mkdir(path.join(appDir, "inline_scripts"), { recursive: true });
|
||||
|
||||
@@ -131,6 +131,16 @@ async function createRawAppOnDisk(appDir: string): Promise<void> {
|
||||
INLINE_SCRIPT_A_LOCK,
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
// Optionally create backend runnable (type: inline)
|
||||
if (includeBackend) {
|
||||
await mkdir(path.join(appDir, "backend"), { recursive: true });
|
||||
await writeFile(path.join(appDir, "backend", "query.yaml"), "type: inline\n", "utf-8");
|
||||
await writeFile(path.join(appDir, "backend", "query.ts"), `export async function main(x: number): Promise<string> {
|
||||
return \`Result: \${x}\`;
|
||||
}
|
||||
`, "utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
test("Raw App: full sync workflow - push, pull, modify, push, clear, pull", async () => {
|
||||
@@ -153,7 +163,7 @@ excludes: []`, "utf-8");
|
||||
// Create folder structure
|
||||
const appDir = path.join(tempDir, "f", "test", "my_raw_app.raw_app");
|
||||
await mkdir(path.join(tempDir, "f", "test"), { recursive: true });
|
||||
await createRawAppOnDisk(appDir);
|
||||
await createRawAppOnDisk(appDir, true); // Include backend for metadata test
|
||||
|
||||
// =========================================================================
|
||||
// STEP 1: Initial push - create raw app on backend
|
||||
@@ -266,6 +276,47 @@ excludes: []`, "utf-8");
|
||||
|
||||
const pulledInlineScript = await readFileContent(inlineScriptPath);
|
||||
expect(pulledInlineScript).toContain("modified:");
|
||||
|
||||
// =========================================================================
|
||||
// STEP 7: Test that script generate-metadata does NOT process backend runnables
|
||||
// =========================================================================
|
||||
|
||||
// Create a standalone script (should be processed by script generate-metadata)
|
||||
await writeFile(path.join(tempDir, "f", "test", "standalone.ts"), `export async function main(): Promise<string> {
|
||||
return "hello";
|
||||
}
|
||||
`, "utf-8");
|
||||
|
||||
// Run script generate-metadata
|
||||
const metaResult1 = await backend.runCLICommand(
|
||||
['script', 'generate-metadata', '--yes'],
|
||||
tempDir, "raw_app_test"
|
||||
);
|
||||
expect(metaResult1.code).toEqual(0);
|
||||
|
||||
// Run generate-metadata --skip-flows --skip-apps
|
||||
const metaResult2 = await backend.runCLICommand(
|
||||
['generate-metadata', '--skip-flows', '--skip-apps', '--yes'],
|
||||
tempDir, "raw_app_test"
|
||||
);
|
||||
expect(metaResult2.code).toEqual(0);
|
||||
|
||||
// Backend runnables should NOT have .script.yaml files
|
||||
const backendDir = path.join(appDir, "backend");
|
||||
expect(await fileExists(path.join(backendDir, "query.yaml"))).toBeTruthy();
|
||||
expect(await fileExists(path.join(backendDir, "query.ts"))).toBeTruthy();
|
||||
expect(await fileExists(path.join(backendDir, "query.script.yaml"))).toBeFalsy();
|
||||
expect(await fileExists(path.join(backendDir, "query.script.lock"))).toBeFalsy();
|
||||
|
||||
// Bug: raw app backend files get misprocessed and create script files at wrong location
|
||||
// The path f/test/my_raw_app.raw_app/backend/query.ts gets truncated at first "."
|
||||
// becoming f/test/my_raw_app.script.yaml (stripping .raw_app/backend/query.ts)
|
||||
expect(await fileExists(path.join(tempDir, "f", "test", "my_raw_app.script.yaml"))).toBeFalsy();
|
||||
expect(await fileExists(path.join(tempDir, "f", "test", "my_raw_app.script.lock"))).toBeFalsy();
|
||||
|
||||
// Standalone script SHOULD have metadata
|
||||
expect(await fileExists(path.join(tempDir, "f", "test", "standalone.script.yaml"))).toBeTruthy();
|
||||
expect(await fileExists(path.join(tempDir, "f", "test", "standalone.script.lock"))).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
70
frontend/package-lock.json
generated
70
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.655.0",
|
||||
"version": "1.657.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.655.0",
|
||||
"version": "1.657.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
@@ -81,11 +81,11 @@
|
||||
"windmill-parser-wasm-java": "1.510.1",
|
||||
"windmill-parser-wasm-nu": "1.510.1",
|
||||
"windmill-parser-wasm-php": "1.647.1",
|
||||
"windmill-parser-wasm-py": "1.653.0",
|
||||
"windmill-parser-wasm-py": "1.655.0",
|
||||
"windmill-parser-wasm-regex": "1.653.0",
|
||||
"windmill-parser-wasm-ruby": "1.526.1",
|
||||
"windmill-parser-wasm-rust": "1.647.1",
|
||||
"windmill-parser-wasm-ts": "1.653.0",
|
||||
"windmill-parser-wasm-ts": "1.655.0",
|
||||
"windmill-parser-wasm-yaml": "1.593.0",
|
||||
"windmill-sql-datatype-parser-wasm": "1.512.0",
|
||||
"windmill-utils-internal": "^1.3.4",
|
||||
@@ -837,6 +837,7 @@
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -848,6 +849,7 @@
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz",
|
||||
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -858,6 +860,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
|
||||
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -1347,6 +1350,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
|
||||
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -1503,6 +1507,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1519,6 +1524,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1535,6 +1541,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1551,6 +1558,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1567,6 +1575,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1583,6 +1592,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1599,6 +1609,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1615,6 +1626,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1631,6 +1643,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1647,6 +1660,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1663,6 +1677,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1679,6 +1694,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1695,6 +1711,7 @@
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -1711,6 +1728,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1727,6 +1745,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2032,6 +2051,7 @@
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -6840,7 +6860,7 @@
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
@@ -7339,6 +7359,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7359,6 +7380,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7379,6 +7401,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7399,6 +7422,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7419,6 +7443,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7439,6 +7464,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7459,6 +7485,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7479,6 +7506,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7499,6 +7527,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7519,6 +7548,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -7539,6 +7569,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -12114,6 +12145,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-eslint-parser": {
|
||||
"version": "0.43.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz",
|
||||
@@ -12844,7 +12890,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -13625,9 +13671,9 @@
|
||||
"integrity": "sha512-u2qaMkupSdhJibxvkLh3r/y36IARvnYNTLXWvOKxcQ0G/BPUB4+yF5o/yf47vv9zUV5WZv4mrdsKDt/pZDYeDg=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-py": {
|
||||
"version": "1.653.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.653.0.tgz",
|
||||
"integrity": "sha512-vMkSL3JpELpag7nmyGA8onhYNiAG3K1mkh2k4vwVHC3W5dUd12fSS9gsBco2FqPGUPnWv1gCaHwmjBrOBVGL1w=="
|
||||
"version": "1.655.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.655.0.tgz",
|
||||
"integrity": "sha512-kWpAE2Me/8KwNqq4n+0ZoEqMJZ9aCZf1744j0S6Om+F+XyHFR3e7GYK/slJ4cF7zA9TF6mKUSiW4oHJ+WUlNXQ=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-regex": {
|
||||
"version": "1.653.0",
|
||||
@@ -13645,9 +13691,9 @@
|
||||
"integrity": "sha512-9yGLYZX2Hn9TdTqGY/5Fp50ftzgUsrfBkSK9vJkKJd5Amyg+yXLBGzd8pz6Org+4uxMenz/16wpsgijvo6uhhQ=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-ts": {
|
||||
"version": "1.653.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.653.0.tgz",
|
||||
"integrity": "sha512-zwBUy7ijo58ooAKcsYflISY/+xllCw3Aq34Kj1PED6uABWVbV6A8MWHqEsjiVzC6iWfihWNtZJpck8zsRr9DCg=="
|
||||
"version": "1.655.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.655.0.tgz",
|
||||
"integrity": "sha512-nzvxILV68MccOKuZcGwjZLB+mWzPMMqEP0FTOb9nmkRWzKPFMxs5ADAXrDZINeNLNv64hYQhGw66ouq0P1G6jQ=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-yaml": {
|
||||
"version": "1.593.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.655.0",
|
||||
"version": "1.657.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -154,11 +154,11 @@
|
||||
"windmill-parser-wasm-java": "1.510.1",
|
||||
"windmill-parser-wasm-nu": "1.510.1",
|
||||
"windmill-parser-wasm-php": "1.647.1",
|
||||
"windmill-parser-wasm-py": "1.653.0",
|
||||
"windmill-parser-wasm-py": "1.655.0",
|
||||
"windmill-parser-wasm-regex": "1.653.0",
|
||||
"windmill-parser-wasm-ruby": "1.526.1",
|
||||
"windmill-parser-wasm-rust": "1.647.1",
|
||||
"windmill-parser-wasm-ts": "1.653.0",
|
||||
"windmill-parser-wasm-ts": "1.655.0",
|
||||
"windmill-parser-wasm-yaml": "1.593.0",
|
||||
"windmill-sql-datatype-parser-wasm": "1.512.0",
|
||||
"windmill-utils-internal": "^1.3.4",
|
||||
@@ -583,4 +583,4 @@
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.35.0",
|
||||
"fsevents": "^2.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,7 +291,12 @@
|
||||
workers.some(([_, pings]) => pings.some((p) => p.native_mode === true)))
|
||||
)
|
||||
let nonNativeTags = $derived(
|
||||
(nconfig?.worker_tags ?? []).filter((t) => !nativeTags.includes(t) && t !== 'flow')
|
||||
(nconfig?.worker_tags ?? []).filter(
|
||||
(t) =>
|
||||
!nativeTags.some((nt) => t === nt || t.startsWith(`${nt}-`)) &&
|
||||
t !== 'flow' &&
|
||||
!t.startsWith('flow-')
|
||||
)
|
||||
)
|
||||
let isAutoNativeMode = $derived(name === 'native')
|
||||
let isNativeModeEnabled = $derived(nconfig?.native_mode === true || isAutoNativeMode)
|
||||
@@ -569,8 +574,8 @@
|
||||
<Alert size="xs" type="warning" title="Non-native tags detected">
|
||||
This worker group has native mode enabled but includes non-native tags: {nonNativeTags.join(
|
||||
', '
|
||||
)}. Non-native jobs will be failed. This is fine if those custom tags are only used
|
||||
for native language jobs.
|
||||
)}. This is fine if jobs sent to those tags are native only, otherwise they will be
|
||||
failed.
|
||||
</Alert>
|
||||
{/if}
|
||||
{#if isNativeModeEnabled && nconfig?.worker_tags != undefined && !nconfig.worker_tags.includes(defaultTagPerWorkspace && workspaceTag ? `flow-${workspaceTag}` : 'flow')}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<script lang="ts" module>
|
||||
export let openedDrawers: { val: string[] } = $state({ val: [] })
|
||||
|
||||
// When a disposable with minZIndex is open, all disposables use that as
|
||||
// their z-index base so that overlays opened on top (e.g. a Drawer from
|
||||
// inside a Modal) stack correctly above it.
|
||||
// We track per-id entries so concurrent modals don't clobber each other
|
||||
// (closing one must not reset the base while another is still open).
|
||||
let minZIndexEntries: Record<string, number> = $state({})
|
||||
let activeMinZIndex = $derived.by(() => {
|
||||
const values = Object.values(minZIndexEntries)
|
||||
return values.length > 0 ? Math.max(...values) : 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -11,6 +22,11 @@
|
||||
id?: any
|
||||
preventEscape?: boolean
|
||||
initialOffset?: number
|
||||
/** Minimum z-index base for this overlay. While any disposable with a
|
||||
* minZIndex is open, all disposables use that as their base so that
|
||||
* subsequent overlays stack above it (e.g. zIndexes.aiChat + 1 for
|
||||
* modals that need to render above the AI chat panel). */
|
||||
minZIndex?: number
|
||||
children?: import('svelte').Snippet<[any]>
|
||||
onOpen?: () => void
|
||||
onClose?: () => void
|
||||
@@ -21,13 +37,17 @@
|
||||
id = (Math.random() + 1).toString(36).substring(10),
|
||||
preventEscape = false,
|
||||
initialOffset = 0,
|
||||
minZIndex = 0,
|
||||
children,
|
||||
onOpen,
|
||||
onClose
|
||||
}: Props = $props()
|
||||
|
||||
let offset = $state(untrack(() => initialOffset))
|
||||
let zIndex = $derived(zIndexes.disposables + offset)
|
||||
// Note: when a Modal with minZIndex is open, all disposables (including
|
||||
// already-open Drawers) are elevated. This is acceptable — relative
|
||||
// stacking order is preserved by the per-instance offset.
|
||||
let zIndex = $derived(Math.max(zIndexes.disposables, activeMinZIndex) + offset)
|
||||
|
||||
export function toggleDrawer() {
|
||||
if (!open) {
|
||||
@@ -44,6 +64,9 @@
|
||||
}
|
||||
openedDrawers.val.push(id)
|
||||
offset = initialOffset + openedDrawers.val.length
|
||||
if (minZIndex > 0) {
|
||||
minZIndexEntries[id] = minZIndex
|
||||
}
|
||||
}
|
||||
|
||||
export function closeDrawer() {
|
||||
@@ -51,6 +74,9 @@
|
||||
offset = initialOffset
|
||||
if (openedDrawers.val.includes(id)) {
|
||||
openedDrawers.val = openedDrawers.val.filter((drawer) => drawer !== id)
|
||||
if (minZIndex > 0) {
|
||||
delete minZIndexEntries[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +115,9 @@
|
||||
if (open) {
|
||||
openedDrawers.val.push(untrack(() => id))
|
||||
offset = untrack(() => initialOffset) + openedDrawers.val.length
|
||||
if (minZIndex > 0) {
|
||||
minZIndexEntries[untrack(() => id)] = minZIndex
|
||||
}
|
||||
}
|
||||
|
||||
let wasEverOpen = false
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
import { createBubbler, stopPropagation } from 'svelte/legacy'
|
||||
|
||||
const bubble = createBubbler()
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Button from '../button/Button.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import CloseButton from '../CloseButton.svelte'
|
||||
import Disposable from '../drawer/Disposable.svelte'
|
||||
import { zIndexes } from '$lib/zIndexes'
|
||||
import { chatState } from '$lib/components/copilot/chat/sharedChatState.svelte'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
@@ -28,12 +31,28 @@
|
||||
cancelText = undefined,
|
||||
kind = 'button',
|
||||
settings,
|
||||
children,
|
||||
children: children_render,
|
||||
actions
|
||||
}: Props = $props()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let disposable: Disposable | undefined = $state(undefined)
|
||||
|
||||
// Only elevate above the AI chat panel when it's actually open —
|
||||
// when chat is closed there's nothing at z-index 1200 to stack above.
|
||||
const minZIndex = $derived(chatState.size > 0 ? zIndexes.aiChat + 1 : 0)
|
||||
|
||||
// Both `bind:open` and this $effect are needed: bind:open syncs the
|
||||
// boolean, while the effect calls openDrawer/closeDrawer to register
|
||||
// the disposable in the stacking system (same pattern as Drawer.svelte).
|
||||
$effect(() => {
|
||||
open
|
||||
untrack(() => {
|
||||
open ? disposable?.openDrawer() : disposable?.closeDrawer()
|
||||
})
|
||||
})
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (open) {
|
||||
switch (event.key) {
|
||||
@@ -58,70 +77,80 @@
|
||||
|
||||
<svelte:window onkeydowncapture={onKeyDown} />
|
||||
|
||||
{#if open}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
onclick={() => (open = false)}
|
||||
transition:fadeFast|local
|
||||
class={'fixed top-0 bottom-0 left-0 right-0 z-[9999]'}
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class={twMerge(
|
||||
'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity',
|
||||
open ? 'ease-out duration-300 opacity-100' : 'ease-in duration-200 opacity-0'
|
||||
)}
|
||||
></div>
|
||||
|
||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div class="flex min-h-full items-center justify-center p-4">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<Disposable bind:open bind:this={disposable} preventEscape {minZIndex}>
|
||||
{#snippet children({ zIndex })}
|
||||
{#if open}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
onclick={() => (open = false)}
|
||||
transition:fadeFast|local
|
||||
class="fixed top-0 bottom-0 left-0 right-0"
|
||||
style="z-index: {zIndex}"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
onclick={stopPropagation(bubble('click'))}
|
||||
class={twMerge(
|
||||
'relative transform overflow-hidden rounded-md bg-surface px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6',
|
||||
c,
|
||||
'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity',
|
||||
open
|
||||
? 'ease-out duration-300 opacity-100 translate-y-0 sm:scale-100'
|
||||
: 'ease-in duration-200 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
|
||||
? 'ease-out duration-300 opacity-100'
|
||||
: 'ease-in duration-200 opacity-0'
|
||||
)}
|
||||
{style}
|
||||
>
|
||||
{#if kind == 'X'}
|
||||
<div class="absolute top-4 right-4"><CloseButton on:close={() => (open = false)} /></div
|
||||
>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
<div class="text-left flex-1">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h3 class="text-emphasis text-lg font-semibold">{title}</h3>
|
||||
{@render settings?.()}
|
||||
</div>
|
||||
></div>
|
||||
|
||||
<div class="mt-4 text-sm text-primary">
|
||||
{@render children?.()}
|
||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div class="flex min-h-full items-center justify-center p-4">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
onclick={stopPropagation(bubble('click'))}
|
||||
class={twMerge(
|
||||
'relative transform overflow-hidden rounded-md bg-surface px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6',
|
||||
c,
|
||||
open
|
||||
? 'ease-out duration-300 opacity-100 translate-y-0 sm:scale-100'
|
||||
: 'ease-in duration-200 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
|
||||
)}
|
||||
{style}
|
||||
>
|
||||
{#if kind == 'X'}
|
||||
<div class="absolute top-4 right-4"
|
||||
><CloseButton on:close={() => (open = false)} /></div
|
||||
>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
<div class="text-left flex-1">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h3 class="text-emphasis text-lg font-semibold">{title}</h3>
|
||||
{@render settings?.()}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-sm text-primary">
|
||||
{@render children_render?.()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if kind == 'button'}
|
||||
<div
|
||||
class="flex items-center space-x-2 flex-row-reverse space-x-reverse mt-4"
|
||||
>
|
||||
{@render actions?.()}
|
||||
<Button
|
||||
on:click={() => {
|
||||
dispatch('canceled')
|
||||
open = false
|
||||
}}
|
||||
color="light"
|
||||
size="sm"
|
||||
>
|
||||
{cancelText ?? 'Cancel'}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if kind == 'button'}
|
||||
<div class="flex items-center space-x-2 flex-row-reverse space-x-reverse mt-4">
|
||||
{@render actions?.()}
|
||||
<Button
|
||||
on:click={() => {
|
||||
dispatch('canceled')
|
||||
open = false
|
||||
}}
|
||||
color="light"
|
||||
size="sm"
|
||||
>
|
||||
{cancelText ?? 'Cancel'}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Disposable>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { yamlStringifyExceptKeys } from './utils'
|
||||
import type { ChatCompletionMessageParam } from 'openai/resources/index.mjs'
|
||||
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
|
||||
import { validateToolName } from '$lib/components/graph/renderers/nodes/AIToolNode.svelte'
|
||||
import { getToolNameError } from '$lib/components/graph/renderers/nodes/AIToolNode.svelte'
|
||||
import {
|
||||
inputBaseClass,
|
||||
inputBorderClass,
|
||||
@@ -117,6 +117,7 @@ Generate a tool name for the script below:
|
||||
elementProps?: Record<string, any>
|
||||
class?: string
|
||||
onChange?: (content: string) => void
|
||||
siblingToolNames?: string[]
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -130,9 +131,16 @@ Generate a tool name for the script below:
|
||||
elementType = 'input',
|
||||
elementProps = {},
|
||||
class: clazz = '',
|
||||
onChange = undefined
|
||||
onChange = undefined,
|
||||
siblingToolNames = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let toolNameError = $derived(
|
||||
promptConfigName === 'agentToolFunctionName'
|
||||
? getToolNameError(content ?? '', undefined, siblingToolNames)
|
||||
: undefined
|
||||
)
|
||||
|
||||
let el: HTMLElement | undefined = $state()
|
||||
let generatedContent = $state('')
|
||||
let active = $state(false)
|
||||
@@ -347,16 +355,16 @@ Generate a tool name for the script below:
|
||||
inputBaseClass,
|
||||
inputSizeClasses.md,
|
||||
inputBorderClass({
|
||||
error: promptConfigName === 'agentToolFunctionName' && !validateToolName(content ?? '')
|
||||
error: !!toolNameError
|
||||
}),
|
||||
'w-full'
|
||||
)}
|
||||
onfocus={() => (focused = true)}
|
||||
onblur={() => (focused = false)}
|
||||
/>
|
||||
{#if promptConfigName === 'agentToolFunctionName' && !validateToolName(content ?? '')}
|
||||
{#if toolNameError}
|
||||
<p class="text-3xs text-red-400 leading-tight mt-0.5">
|
||||
Invalid tool name, should only contain letters, numbers and underscores
|
||||
{toolNameError}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -30,11 +30,37 @@
|
||||
fontClass?: string
|
||||
} = $props()
|
||||
|
||||
// Get list of array-type arguments from schema
|
||||
// Check if an originalType like "string | string[]" is a top-level
|
||||
// union where at least one member is an array type (ends with "[]").
|
||||
// Splits on "|" only at the top level (not inside {}, <>, or ()).
|
||||
function isUnionWithArray(originalType: string | undefined): boolean {
|
||||
if (!originalType) return false
|
||||
let depth = 0
|
||||
const parts: string[] = []
|
||||
let cur = ''
|
||||
for (const ch of originalType) {
|
||||
if (ch === '{' || ch === '<' || ch === '(') depth++
|
||||
else if (ch === '}' || ch === '>' || ch === ')') depth--
|
||||
else if (ch === '|' && depth === 0) {
|
||||
parts.push(cur.trim())
|
||||
cur = ''
|
||||
continue
|
||||
}
|
||||
cur += ch
|
||||
}
|
||||
parts.push(cur.trim())
|
||||
// Match TS array syntax (T[]) and Python list syntax (list[T] / List[T])
|
||||
return parts.length > 1 && parts.some((p) => p.endsWith('[]') || /^[Ll]ist\[.+\]$/.test(p))
|
||||
}
|
||||
|
||||
// Get list of arguments eligible for accumulation from schema.
|
||||
// Includes array-type arguments and union types like T | T[]
|
||||
// whose scalar values are wrapped into single-element arrays
|
||||
// at aggregation time.
|
||||
let arrayArgs = $derived(
|
||||
schema?.properties
|
||||
? Object.entries(schema.properties)
|
||||
.filter(([_, prop]) => prop.type === 'array')
|
||||
.filter(([_, prop]) => prop.type === 'array' || isUnionWithArray(prop.originalType))
|
||||
.map(([key, _]) => key)
|
||||
: []
|
||||
)
|
||||
@@ -111,8 +137,8 @@
|
||||
<Label label="Argument to accumulate (optional)">
|
||||
{#snippet header()}
|
||||
<Tooltip>
|
||||
Select a list-type argument to accumulate across debounced executions. Values from
|
||||
each debounced execution will be appended together.</Tooltip
|
||||
Select a list-type argument to accumulate across debounced executions. Values from each
|
||||
debounced execution will be appended together.</Tooltip
|
||||
>
|
||||
{/snippet}
|
||||
<select disabled={!$enterpriseLicense} bind:value={selectedArg}>
|
||||
@@ -131,8 +157,8 @@
|
||||
<Label label="Max total debouncing time (optional)">
|
||||
{#snippet header()}
|
||||
<Tooltip>
|
||||
Maximum total time (in seconds) that a job can be debounced before it must execute.
|
||||
Once this time is reached, the job will run regardless of ongoing debouncing.</Tooltip
|
||||
Maximum total time (in seconds) that a job can be debounced before it must execute. Once
|
||||
this time is reached, the job will run regardless of ongoing debouncing.</Tooltip
|
||||
>
|
||||
{/snippet}
|
||||
<SecondsInput disabled={!$enterpriseLicense} bind:seconds={max_total_debouncing_time} />
|
||||
@@ -140,11 +166,16 @@
|
||||
<Label label="Max total debounces amount (optional)">
|
||||
{#snippet header()}
|
||||
<Tooltip>
|
||||
Maximum number of times a job can be debounced before it must execute. Once this
|
||||
count is reached, the job will run regardless of ongoing debouncing.</Tooltip
|
||||
Maximum number of times a job can be debounced before it must execute. Once this count
|
||||
is reached, the job will run regardless of ongoing debouncing.</Tooltip
|
||||
>
|
||||
{/snippet}
|
||||
<input type="number" disabled={!$enterpriseLicense} bind:value={max_total_debounces_amount} min="0" />
|
||||
<input
|
||||
type="number"
|
||||
disabled={!$enterpriseLicense}
|
||||
bind:value={max_total_debounces_amount}
|
||||
min="0"
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
action?: import('svelte').Snippet
|
||||
children?: import('svelte').Snippet
|
||||
isAgentTool?: boolean
|
||||
siblingToolNames?: string[]
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -23,7 +24,8 @@
|
||||
header,
|
||||
action,
|
||||
children,
|
||||
isAgentTool = false
|
||||
isAgentTool = false,
|
||||
siblingToolNames = undefined
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
@@ -38,6 +40,7 @@
|
||||
{flowModuleValue}
|
||||
{action}
|
||||
{isAgentTool}
|
||||
{siblingToolNames}
|
||||
>
|
||||
{@render header?.()}
|
||||
</FlowCardHeader>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import { Flag, Lock, RefreshCw, Unlock } from 'lucide-svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { validateToolName } from '$lib/components/graph/renderers/nodes/AIToolNode.svelte'
|
||||
import { getToolNameError } from '$lib/components/graph/renderers/nodes/AIToolNode.svelte'
|
||||
import { DEFAULT_HUB_BASE_URL, PRIVATE_HUB_MIN_VERSION } from '$lib/hub'
|
||||
|
||||
interface Props {
|
||||
@@ -28,6 +28,7 @@
|
||||
children?: import('svelte').Snippet
|
||||
action?: import('svelte').Snippet
|
||||
isAgentTool?: boolean
|
||||
siblingToolNames?: string[]
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -36,9 +37,14 @@
|
||||
summary = $bindable(undefined),
|
||||
children,
|
||||
action,
|
||||
isAgentTool = false
|
||||
isAgentTool = false,
|
||||
siblingToolNames = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let toolNameError = $derived(
|
||||
isAgentTool ? getToolNameError(summary ?? '', undefined, siblingToolNames) : undefined
|
||||
)
|
||||
|
||||
let latestHash: string | undefined = $state(undefined)
|
||||
|
||||
// Extract version_id from hub path (format: hub/{version_id}/{app}/{summary})
|
||||
@@ -103,6 +109,7 @@
|
||||
elementProps={{
|
||||
placeholder: isAgentTool ? 'Tool name' : 'Summary'
|
||||
}}
|
||||
{siblingToolNames}
|
||||
/>
|
||||
{:else if flowModuleValue.type === 'script' && 'path' in flowModuleValue && flowModuleValue.path}
|
||||
<IconedPath path={flowModuleValue.path} hash={flowModuleValue.hash} class="grow" />
|
||||
@@ -173,14 +180,16 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<input
|
||||
bind:value={summary}
|
||||
placeholder={isAgentTool ? 'Tool name' : 'Summary'}
|
||||
class={twMerge(
|
||||
'w-full grow',
|
||||
isAgentTool && !validateToolName(summary ?? '') && '!border-red-400'
|
||||
)}
|
||||
/>
|
||||
<div class="flex flex-col w-full grow">
|
||||
<input
|
||||
bind:value={summary}
|
||||
placeholder={isAgentTool ? 'Tool name' : 'Summary'}
|
||||
class={twMerge('w-full grow', toolNameError && '!border-red-400')}
|
||||
/>
|
||||
{#if toolNameError}
|
||||
<p class="text-3xs text-red-400 leading-tight mt-0.5">{toolNameError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if flowModuleValue.type === 'flow'}
|
||||
<Badge color="indigo" capitalize>flow</Badge>
|
||||
<input bind:value={summary} placeholder="Summary" class="w-full grow" />
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
previousModule?: FlowModule | undefined
|
||||
forceTestTab?: Record<string, boolean>
|
||||
highlightArg?: Record<string, string | undefined>
|
||||
siblingToolNames?: string[]
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -23,7 +24,8 @@
|
||||
parentModule = undefined,
|
||||
previousModule = undefined,
|
||||
forceTestTab,
|
||||
highlightArg
|
||||
highlightArg,
|
||||
siblingToolNames = undefined
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
@@ -43,6 +45,7 @@
|
||||
forceTestTab={forceTestTab?.[tool.id]}
|
||||
highlightArg={highlightArg?.[tool.id]}
|
||||
isAgentTool={true}
|
||||
{siblingToolNames}
|
||||
/>
|
||||
{:else if isMcpTool(tool)}
|
||||
<!-- MCP tool - use McpToolEditor -->
|
||||
|
||||
@@ -101,9 +101,7 @@
|
||||
)
|
||||
let canMoveSelected = $derived(
|
||||
resolvedModuleIds.length > 0 &&
|
||||
areContiguousSiblings(
|
||||
locateModules(resolvedModuleIds, flowStore.val.value.modules ?? [])
|
||||
)
|
||||
areContiguousSiblings(locateModules(resolvedModuleIds, flowStore.val.value.modules ?? []))
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
forceTestTab?: boolean
|
||||
highlightArg?: string
|
||||
isAgentTool?: boolean
|
||||
siblingToolNames?: string[]
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -125,7 +126,8 @@
|
||||
savedModule = undefined,
|
||||
forceTestTab = false,
|
||||
highlightArg = undefined,
|
||||
isAgentTool = false
|
||||
isAgentTool = false,
|
||||
siblingToolNames = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let workspaceScriptTag: string | undefined = $state(undefined)
|
||||
@@ -237,7 +239,9 @@
|
||||
}
|
||||
|
||||
let forceReload = $state(0)
|
||||
let editorPanelSize = $state(untrack(() => noEditor) ? 0 : flowModule.value.type == 'script' ? 30 : 50)
|
||||
let editorPanelSize = $state(
|
||||
untrack(() => noEditor) ? 0 : flowModule.value.type == 'script' ? 30 : 50
|
||||
)
|
||||
let editorSettingsPanelSize = $state(100 - untrack(() => editorPanelSize))
|
||||
let stepHistoryLoader = getStepHistoryLoaderContext()
|
||||
|
||||
@@ -726,6 +730,7 @@
|
||||
}}
|
||||
bind:summary={flowModule.summary}
|
||||
{isAgentTool}
|
||||
{siblingToolNames}
|
||||
>
|
||||
{#snippet header()}
|
||||
<FlowModuleHeader
|
||||
@@ -1062,8 +1067,8 @@
|
||||
{enableAi}
|
||||
{isAgentTool}
|
||||
allowedAiTransforms={isAgentTool && flowModule.value.type === 'aiagent'
|
||||
? ['user_message']
|
||||
: undefined}
|
||||
? ['user_message']
|
||||
: undefined}
|
||||
helperScript={retrieveDynCodeAndLang(flowModule.value)}
|
||||
chatInputEnabled={flowStore.val.value?.chat_input_enabled ?? false}
|
||||
/>
|
||||
|
||||
@@ -305,6 +305,7 @@
|
||||
{enableAi}
|
||||
{forceTestTab}
|
||||
{highlightArg}
|
||||
siblingToolNames={flowModule.value.tools.map((t) => t.summary ?? '')}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -300,6 +300,7 @@ export type AiToolN = {
|
||||
data: {
|
||||
tool: string
|
||||
type?: string
|
||||
nameError?: string
|
||||
eventHandlers: GraphEventHandlers
|
||||
moduleId: string
|
||||
insertable: boolean
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
<script module lang="ts">
|
||||
export function validateToolName(name: string, type?: string) {
|
||||
if (type === 'websearch') return true
|
||||
import { forbiddenIds } from '$lib/components/flows/idUtils'
|
||||
|
||||
export function getToolNameError(
|
||||
name: string,
|
||||
type?: string,
|
||||
siblingNames?: string[]
|
||||
): string | undefined {
|
||||
if (type === 'websearch') return undefined
|
||||
if (type === 'mcp') {
|
||||
return name.length > 0
|
||||
return name.length > 0 ? undefined : 'Tool name must not be empty'
|
||||
}
|
||||
return /^[a-zA-Z0-9_]+$/.test(name)
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(name)) {
|
||||
return 'Tool name must only contain letters, numbers and underscores'
|
||||
}
|
||||
if (forbiddenIds.includes(name)) {
|
||||
return `'${name}' is a reserved name`
|
||||
}
|
||||
if (siblingNames && siblingNames.filter((n) => n === name).length > 1) {
|
||||
return 'Duplicate tool name'
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function validateToolName(name: string, type?: string) {
|
||||
return getToolNameError(name, type) === undefined
|
||||
}
|
||||
|
||||
export const AI_TOOL_BASE_OFFSET = 5
|
||||
@@ -147,6 +166,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const siblingNames = tools.map((t) => t.name)
|
||||
const toolNodes: (Node & AiToolN)[] = tools.map((tool, i) => {
|
||||
let inputToolXGap = 12
|
||||
let inputToolWidth = (ROW_WIDTH - inputToolXGap) / 2
|
||||
@@ -160,6 +180,7 @@
|
||||
data: {
|
||||
tool: tool.name,
|
||||
type: tool.type,
|
||||
nameError: getToolNameError(tool.name, tool.type, siblingNames),
|
||||
eventHandlers,
|
||||
moduleId: tool.id,
|
||||
insertable,
|
||||
@@ -169,13 +190,13 @@
|
||||
width: inputToolWidth,
|
||||
position: {
|
||||
x:
|
||||
(tools.length === 1
|
||||
tools.length === 1
|
||||
? (ROW_WIDTH - inputToolWidth) / 2
|
||||
: (i + 1) % 2 === 0
|
||||
? inputToolWidth + inputToolXGap
|
||||
: isLastRow && tools.length % 2 === 1
|
||||
? (ROW_WIDTH - inputToolWidth) / 2
|
||||
: 0),
|
||||
: 0,
|
||||
y:
|
||||
baseOffset +
|
||||
rowOffset *
|
||||
@@ -287,7 +308,7 @@
|
||||
const flowModuleState = $derived(data.flowModuleStates?.[data.moduleId])
|
||||
let colorClasses = $derived(
|
||||
getNodeColorClasses(
|
||||
!validateToolName(data.tool, data.type) ? 'Failure' : flowModuleState?.type,
|
||||
data.nameError ? 'Failure' : flowModuleState?.type,
|
||||
selectionManager?.getSelectedId() === data.moduleId
|
||||
)
|
||||
)
|
||||
@@ -324,12 +345,7 @@
|
||||
<Wrench size={16} class="ml-1 shrink-0" />
|
||||
{/if}
|
||||
|
||||
<span
|
||||
class={twMerge(
|
||||
'text-3xs truncate flex-1',
|
||||
!validateToolName(data.tool, data.type) && 'text-red-400'
|
||||
)}
|
||||
>
|
||||
<span class={twMerge('text-3xs truncate flex-1', data.nameError && 'text-red-400')}>
|
||||
{data.tool || 'Missing name'}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import { FlowService, ScriptService, UserService, type TruncatedToken } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
@@ -10,23 +10,21 @@
|
||||
import { capitalize } from '$lib/utils'
|
||||
|
||||
interface Props {
|
||||
isFlow: boolean;
|
||||
path: string;
|
||||
labelPrefix: 'email' | 'webhook';
|
||||
isFlow: boolean
|
||||
path: string
|
||||
labelPrefix: 'email' | 'webhook'
|
||||
}
|
||||
|
||||
let { isFlow, path, labelPrefix }: Props = $props();
|
||||
let { isFlow, path, labelPrefix }: Props = $props()
|
||||
|
||||
const { triggersCount } = getContext<TriggerContext>('TriggerContext')
|
||||
|
||||
let tokens: TruncatedToken[] | undefined = $state(undefined)
|
||||
|
||||
export async function listTokens() {
|
||||
tokens = (
|
||||
isFlow
|
||||
? await FlowService.listTokensOfFlow({ workspace: $workspaceStore!, path })
|
||||
: await ScriptService.listTokensOfScript({ workspace: $workspaceStore!, path })
|
||||
).filter((x) => x.label && x.label.startsWith(labelPrefix + '-'))
|
||||
tokens = isFlow
|
||||
? await FlowService.listTokensOfFlow({ workspace: $workspaceStore!, path })
|
||||
: await ScriptService.listTokensOfScript({ workspace: $workspaceStore!, path })
|
||||
if (labelPrefix == 'email') {
|
||||
$triggersCount = { ...($triggersCount ?? {}), default_email_count: tokens?.length }
|
||||
} else {
|
||||
@@ -43,14 +41,14 @@
|
||||
|
||||
run(() => {
|
||||
$workspaceStore && listTokens()
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label label="Existing {capitalize(labelPrefix)} Tokens">
|
||||
{#if tokens}
|
||||
{#if tokens.length == 0}
|
||||
<div class="text-xs text-secondary">No {labelPrefix} specific tokens found</div>
|
||||
<div class="text-xs text-secondary">No tokens with matching scopes found</div>
|
||||
{:else}
|
||||
<div class="flex flex-col divide-y pt-2">
|
||||
<div class="grid grid-cols-6 text-2xs items-center py-2">
|
||||
|
||||
@@ -105,17 +105,17 @@ async function initWasmAsset() {
|
||||
|
||||
type InferAssetsResult =
|
||||
| {
|
||||
status: 'ok'
|
||||
assets: AssetWithAccessType[]
|
||||
sql_queries?: InferAssetsSqlQueryDetails[]
|
||||
columns?: Record<string, AssetUsageAccessType>
|
||||
}
|
||||
status: 'ok'
|
||||
assets: AssetWithAccessType[]
|
||||
sql_queries?: InferAssetsSqlQueryDetails[]
|
||||
columns?: Record<string, AssetUsageAccessType>
|
||||
}
|
||||
| {
|
||||
status: 'error'
|
||||
error: string
|
||||
assets?: undefined
|
||||
sql_queries?: undefined
|
||||
}
|
||||
status: 'error'
|
||||
error: string
|
||||
assets?: undefined
|
||||
sql_queries?: undefined
|
||||
}
|
||||
|
||||
export type InferAssetsSqlQueryDetails = {
|
||||
query_string: string // SQL query with $1 placeholders for interpolations
|
||||
@@ -384,6 +384,11 @@ export async function inferArgs(
|
||||
|
||||
argSigToJsonSchemaType(arg.typ, schema.properties[arg.name])
|
||||
|
||||
// For T | T[] detection for debouncing arg accumulation
|
||||
if ((arg as any).otyp && (arg as any).otyp.includes('[') && (arg as any).otyp.includes('|')) {
|
||||
schema.properties[arg.name].originalType = (arg as any).otyp
|
||||
}
|
||||
|
||||
schema.properties[arg.name].default = arg.default
|
||||
|
||||
if (!arg.has_default && !schema.required.includes(arg.name)) {
|
||||
|
||||
@@ -4,7 +4,7 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.655.0"
|
||||
wmill = ">=1.657.0"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: '3.0.3'
|
||||
|
||||
info:
|
||||
version: 1.655.0
|
||||
version: 1.657.0
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.655.0'
|
||||
ModuleVersion = '1.657.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.655.0"
|
||||
version = "1.657.0"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -119,6 +119,23 @@ folder related commands
|
||||
- `folder add-missing` - create default folder.meta.yaml for all subdirectories of f/ that are missing one
|
||||
- `-y, --yes` - skip confirmation prompt
|
||||
|
||||
### generate-metadata
|
||||
|
||||
Generate metadata (locks, schemas) for all scripts, flows, and apps
|
||||
|
||||
**Arguments:** `[folder:string]`
|
||||
|
||||
**Options:**
|
||||
- `--yes` - Skip confirmation prompt
|
||||
- `--dry-run` - Show what would be updated without making changes
|
||||
- `--lock-only` - Re-generate only the lock files
|
||||
- `--schema-only` - Re-generate only script schemas (skips flows and apps)
|
||||
- `--skip-scripts` - Skip processing scripts
|
||||
- `--skip-flows` - Skip processing flows
|
||||
- `--skip-apps` - Skip processing apps
|
||||
- `-i --includes <patterns:file[]>` - Comma separated patterns to specify which files to include
|
||||
- `-e --excludes <patterns:file[]>` - Comma separated patterns to specify which files to exclude
|
||||
|
||||
### gitsync-settings
|
||||
|
||||
Manage git-sync settings between local wmill.yaml and Windmill backend
|
||||
|
||||
@@ -1493,6 +1493,23 @@ folder related commands
|
||||
- \`folder add-missing\` - create default folder.meta.yaml for all subdirectories of f/ that are missing one
|
||||
- \`-y, --yes\` - skip confirmation prompt
|
||||
|
||||
### generate-metadata
|
||||
|
||||
Generate metadata (locks, schemas) for all scripts, flows, and apps
|
||||
|
||||
**Arguments:** \`[folder:string]\`
|
||||
|
||||
**Options:**
|
||||
- \`--yes\` - Skip confirmation prompt
|
||||
- \`--dry-run\` - Show what would be updated without making changes
|
||||
- \`--lock-only\` - Re-generate only the lock files
|
||||
- \`--schema-only\` - Re-generate only script schemas (skips flows and apps)
|
||||
- \`--skip-scripts\` - Skip processing scripts
|
||||
- \`--skip-flows\` - Skip processing flows
|
||||
- \`--skip-apps\` - Skip processing apps
|
||||
- \`-i --includes <patterns:file[]>\` - Comma separated patterns to specify which files to include
|
||||
- \`-e --excludes <patterns:file[]>\` - Comma separated patterns to specify which files to exclude
|
||||
|
||||
### gitsync-settings
|
||||
|
||||
Manage git-sync settings between local wmill.yaml and Windmill backend
|
||||
|
||||
@@ -124,6 +124,23 @@ folder related commands
|
||||
- `folder add-missing` - create default folder.meta.yaml for all subdirectories of f/ that are missing one
|
||||
- `-y, --yes` - skip confirmation prompt
|
||||
|
||||
### generate-metadata
|
||||
|
||||
Generate metadata (locks, schemas) for all scripts, flows, and apps
|
||||
|
||||
**Arguments:** `[folder:string]`
|
||||
|
||||
**Options:**
|
||||
- `--yes` - Skip confirmation prompt
|
||||
- `--dry-run` - Show what would be updated without making changes
|
||||
- `--lock-only` - Re-generate only the lock files
|
||||
- `--schema-only` - Re-generate only script schemas (skips flows and apps)
|
||||
- `--skip-scripts` - Skip processing scripts
|
||||
- `--skip-flows` - Skip processing flows
|
||||
- `--skip-apps` - Skip processing apps
|
||||
- `-i --includes <patterns:file[]>` - Comma separated patterns to specify which files to include
|
||||
- `-e --excludes <patterns:file[]>` - Comma separated patterns to specify which files to exclude
|
||||
|
||||
### gitsync-settings
|
||||
|
||||
Manage git-sync settings between local wmill.yaml and Windmill backend
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.655.0",
|
||||
"version": "1.657.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.655.0",
|
||||
"version": "1.657.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"sideEffects": false,
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.655.0
|
||||
1.657.0
|
||||
|
||||
Reference in New Issue
Block a user