Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
8b26346667 chore(deps): bump actions/setup-go from 2 to 6
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 18:27:16 +00:00
261 changed files with 4458 additions and 14117 deletions

View File

@@ -13,10 +13,8 @@ fi
# Check if the file is in the backend directory and is a Rust file
if [[ "$FILE_PATH" == *"/backend/"* ]] && [[ "$FILE_PATH" =~ \.rs$ ]]; then
cd "$CLAUDE_PROJECT_DIR/backend" || exit 0
# Run rustfmt, surface errors as context but don't block Claude
if rustfmt --config-path rustfmt.toml "$FILE_PATH" 2>&1; then
echo "Formatted $(basename "$FILE_PATH")"
fi
# Run rustfmt with config from rustfmt.toml (edition=2021)
rustfmt --config-path rustfmt.toml "$FILE_PATH" 2>/dev/null || true
fi
exit 0

View File

@@ -15,10 +15,8 @@ if [[ "$FILE_PATH" == *"/frontend/"* ]]; then
# Check if it's a formattable file type
if [[ "$FILE_PATH" =~ \.(ts|js|svelte|json|css|html|md)$ ]]; then
cd "$CLAUDE_PROJECT_DIR/frontend" || exit 0
# Run prettier, surface errors as context but don't block Claude
if ./node_modules/.bin/prettier --plugin prettier-plugin-svelte --write "$FILE_PATH" 2>&1; then
echo "Formatted $(basename "$FILE_PATH")"
fi
# Run prettier silently, don't fail the hook if prettier fails
npx prettier --write "$FILE_PATH" 2>/dev/null || true
fi
fi

View File

@@ -28,12 +28,6 @@
"Bash(git show:*)",
"Bash(git blame:*)",
"Bash(cargo check:*)",
"Bash(cargo build --release:*)",
"Bash(sh wm-ts-nav/nav:*)",
"Bash(wm-ts-nav/nav:*)",
"Bash(./wm-ts-nav/nav:*)",
"Bash(wm-ts-nav/target/release/wm-ts-nav:*)",
"Bash(./wm-ts-nav/target/release/wm-ts-nav:*)",
"mcp__ide__getDiagnostics",
"Bash(npm run generate-backend-client:*)",
"Bash(npm run check:*)",

View File

@@ -1,98 +0,0 @@
---
name: local-review
user_invocable: true
description: Code review a pull request for bugs and CLAUDE.md compliance. MUST use when asked to review code.
---
# Local Code Review Skill
Review a pull request for real bugs and CLAUDE.md compliance violations. This review targets HIGH SIGNAL issues only.
## Review Philosophy
- **Only flag issues you are certain about.** If you are not sure an issue is real, do not flag it. False positives erode trust and waste reviewer time.
- Think like a senior engineer doing a final review — flag things that would cause incidents, not things that are merely imperfect.
## What to Flag
- Code that won't compile or parse (syntax errors, type errors, missing imports)
- Code that will definitely produce wrong results regardless of inputs
- Clear, unambiguous CLAUDE.md violations (quote the exact rule being violated)
- Security issues in introduced code (injection, auth bypass, data exposure)
- Incorrect logic that will fail in production
## What NOT to Flag
- Code style or quality concerns
- Potential issues that depend on specific inputs or runtime state
- Subjective suggestions or improvements
- Pre-existing issues not introduced by this PR
- Pedantic nitpicks a senior engineer wouldn't flag
- Issues a linter or type checker will catch
- General quality concerns unless explicitly prohibited in CLAUDE.md
- Issues silenced via lint ignore comments
## Execution Steps
1. **Determine the PR scope**:
- If an argument is provided, use it as the PR number or branch
- Otherwise, detect from the current branch vs main
- Run `gh pr view` if a PR exists, or use `git diff main...HEAD`
2. **Find relevant CLAUDE.md files**:
- Read the root `CLAUDE.md`
- Check for CLAUDE.md files in directories containing changed files
3. **Get the diff and metadata**:
- `gh pr diff` or `git diff main...HEAD` for the full diff
- `gh pr view` or `git log main..HEAD --oneline` for context
4. **Read changed files** where the diff alone is insufficient to understand context
5. **Review for**:
- CLAUDE.md compliance — check each rule against the changed code
- Bugs and logic errors — will this code work correctly?
- Security issues — injection, auth, data exposure in new code
6. **Self-validate each finding**: Before reporting, ask yourself:
- "Is this definitely a real issue, not a false positive?"
- "Would a senior engineer flag this in review?"
- If the answer to either is no, discard the finding
7. **Output findings** to the terminal (default) or post as PR comments (with `--comment` flag)
## Output Format
```
## Code review
Found N issues:
1. <description> (<reason: CLAUDE.md adherence | bug | security>)
<file_path:line_number>
2. <description> (<reason>)
<file_path:line_number>
```
If no issues are found:
```
## Code review
No issues found. Checked for bugs and CLAUDE.md compliance.
```
## Posting Comments (--comment flag)
If the user passes `--comment`, post findings as inline PR comments using:
```bash
gh pr review --comment --body "<summary>"
```
Or for inline comments on specific lines:
```bash
gh api repos/{owner}/{repo}/pulls/{pr}/reviews -f body="<summary>" -f event="COMMENT" -f comments="[...]"
```

View File

@@ -33,7 +33,6 @@ Follow conventional commit format for the PR title:
- Keep under 70 characters
- Use lowercase, imperative mood
- No period at the end
- If `*_ee.rs` files were modified, prefix with `[ee]`: `[ee] <type>: <description>`
## PR Body Format
@@ -86,25 +85,3 @@ Generated with [Claude Code](https://claude.com/claude-code)
)"
```
7. Return the PR URL to the user
## EE Companion PR (when `*_ee.rs` files were modified)
The `*_ee.rs` files in the windmill repo are **symlinks** to `windmill-ee-private` — changes won't appear in `git diff` of the windmill repo. Instead, check the EE repo for uncommitted or unpushed changes.
Follow the full EE PR workflow in `docs/enterprise.md`. The key PR-specific details:
1. Find the EE repo/worktree: see "Finding the EE Repo" in `docs/enterprise.md`
2. Check for changes: `git -C <ee-path> status --short`
- If there are no changes in the EE repo, skip this entire section
3. Follow steps 15 from the "EE PR Workflow" in `docs/enterprise.md`
4. Create the companion PR (title does NOT get the `[ee]` prefix):
```bash
gh pr create --draft --repo windmill-labs/windmill-ee-private --title "<type>: <description>" --body "$(cat <<'EOF'
Companion PR for windmill-labs/windmill#<PR_NUMBER>
---
Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
```
5. Commit `ee-repo-ref.txt` and push the updated windmill branch

View File

@@ -119,18 +119,6 @@ jobs:
with:
cache-workspaces: backend
toolchain: 1.93.0
- name: Fix stale v8 build cache
working-directory: ./backend
run: |
# Cargo cache may preserve v8 build fingerprints without the actual
# librusty_v8.a library. Since fingerprints look valid, cargo skips
# build.rs re-run, causing "could not find native static library rusty_v8".
for profile in debug release; do
if [ -d "target/$profile/.fingerprint" ] && [ ! -f "target/$profile/gn_out/obj/librusty_v8.a" ]; then
echo "Cleaning stale v8 build artifacts in target/$profile"
rm -rf "target/$profile/build/v8-"* "target/$profile/.fingerprint/v8-"*
fi
done
- name: cargo check
timeout-minutes: 16
working-directory: ./backend

View File

@@ -58,7 +58,7 @@ jobs:
with:
deno-version: v2.x
- uses: actions/setup-go@v2
- uses: actions/setup-go@v6
with:
go-version: 1.21.5

View File

@@ -51,7 +51,7 @@ jobs:
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- uses: actions/setup-go@v2
- uses: actions/setup-go@v6
with:
go-version: 1.21.5
- uses: oven-sh/setup-bun@v2
@@ -89,18 +89,6 @@ jobs:
with:
cache-workspaces: backend
toolchain: 1.93.0
- name: Fix stale v8 build cache
working-directory: ./backend
run: |
# Cargo cache may preserve v8 build fingerprints without the actual
# librusty_v8.a library. Since fingerprints look valid, cargo skips
# build.rs re-run, causing "could not find native static library rusty_v8".
for profile in debug release; do
if [ -d "target/$profile/.fingerprint" ] && [ ! -f "target/$profile/gn_out/obj/librusty_v8.a" ]; then
echo "Cleaning stale v8 build artifacts in target/$profile"
rm -rf "target/$profile/build/v8-"* "target/$profile/.fingerprint/v8-"*
fi
done
- name: Read EE repo commit hash
run: |
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"

View File

@@ -1,37 +0,0 @@
name: Check system prompts freshness
on:
push:
paths:
- "system_prompts/**"
- "typescript-client/**"
- "python-client/wmill/wmill/client.py"
- "openflow.openapi.yaml"
- "backend/windmill-api/openapi.yaml"
- "cli/src/main.ts"
- "cli/src/commands/**"
pull_request:
paths:
- "system_prompts/**"
- "typescript-client/**"
- "python-client/wmill/wmill/client.py"
- "openflow.openapi.yaml"
- "backend/windmill-api/openapi.yaml"
- "cli/src/main.ts"
- "cli/src/commands/**"
jobs:
check-freshness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install pyyaml
- name: Check auto-generated files are up-to-date
run: bash system_prompts/check-freshness.sh

View File

@@ -1,209 +0,0 @@
name: Git Sync Integration Tests
on:
workflow_dispatch:
push:
branches: [main]
paths:
- "backend/windmill-git-sync/**"
- "backend/windmill-api-integration-tests/tests/git_sync*"
- "backend/ee-repo-ref.txt"
- "integration_tests/test/git_sync_test.py"
- ".github/workflows/git-sync-test.yml"
pull_request:
types: [opened, synchronize, reopened]
paths:
- "backend/windmill-git-sync/**"
- "backend/windmill-api-integration-tests/tests/git_sync*"
- "backend/ee-repo-ref.txt"
- "integration_tests/test/git_sync_test.py"
- ".github/workflows/git-sync-test.yml"
concurrency:
group: git-sync-test-${{ github.ref }}
cancel-in-progress: true
jobs:
check-relevance:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if git sync related files changed
id: check
env:
WINDMILL_EE_PRIVATE_ACCESS: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE=${{ github.event.pull_request.base.sha }}
else
BASE=${{ github.event.before }}
fi
CHANGED_FILES=$(git diff --name-only "$BASE"..HEAD 2>/dev/null || echo "")
echo "Changed files:"
echo "$CHANGED_FILES"
# Direct git sync file changes — always relevant
if echo "$CHANGED_FILES" | grep -qE '^(backend/windmill-git-sync/|backend/windmill-api-integration-tests/tests/git_sync|integration_tests/test/git_sync|\.github/workflows/git-sync-test\.yml)'; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "Relevant: direct git sync file changes"
exit 0
fi
# If ee-repo-ref.txt changed, check if the EE diff touches windmill-git-sync/
if echo "$CHANGED_FILES" | grep -q '^backend/ee-repo-ref.txt$'; then
NEW_REF=$(cat backend/ee-repo-ref.txt)
OLD_REF=$(git show "$BASE:backend/ee-repo-ref.txt" 2>/dev/null || echo "")
if [ -n "$OLD_REF" ] && [ "$OLD_REF" != "$NEW_REF" ]; then
# Clone EE repo and check diff
git clone --bare "https://x-access-token:${WINDMILL_EE_PRIVATE_ACCESS}@github.com/windmill-labs/windmill-ee-private.git" /tmp/ee-repo 2>/dev/null
EE_CHANGED=$(git -C /tmp/ee-repo diff --name-only "$OLD_REF".."$NEW_REF" 2>/dev/null || echo "")
echo "EE changed files:"
echo "$EE_CHANGED"
if echo "$EE_CHANGED" | grep -q '^windmill-git-sync/'; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "Relevant: EE git sync files changed"
exit 0
fi
fi
fi
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "No git sync relevant changes detected, skipping tests"
git_sync_e2e:
needs: [check-relevance]
if: needs.check-relevance.outputs.should_run == 'true'
runs-on: ubicloud-standard-16
services:
postgres:
image: postgres:14
ports:
- 5432:5432
env:
POSTGRES_DB: windmill
POSTGRES_PASSWORD: changeme
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Read EE repo commit hash
run: |
echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
repository: windmill-labs/windmill-ee-private
path: ./windmill-ee-private
ref: ${{ env.ee_repo_ref }}
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
fetch-depth: 0
- name: Substitute EE code
run: |
cd backend && ./substitute_ee_code.sh --copy --dir ./windmill-ee-private
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend
toolchain: 1.93.0
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.10
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install wmill CLI
run: |
cd cli && bash gen_wm_client.sh && bun install
mkdir -p "$HOME/.local/bin"
printf '#!/bin/sh\nexec bun run "%s/cli/src/main.ts" "$@"\n' "$GITHUB_WORKSPACE" > "$HOME/.local/bin/wmill"
chmod +x "$HOME/.local/bin/wmill"
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Build Windmill
working-directory: ./backend
env:
SQLX_OFFLINE: true
CARGO_BUILD_JOBS: 12
RUSTFLAGS: ""
run: |
cargo build --features enterprise,private,license,zip
- name: Start Gitea
run: |
docker run -d --name gitea \
-e GITEA__database__DB_TYPE=sqlite3 \
-e GITEA__security__INSTALL_LOCK=true \
-e GITEA__server__HTTP_PORT=3000 \
-e GITEA__server__ROOT_URL=http://localhost:3000 \
-e GITEA__service__DISABLE_REGISTRATION=false \
-p 3000:3000 \
gitea/gitea:1.22-rootless
echo "Waiting for Gitea to be ready..."
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/api/v1/version > /dev/null 2>&1; then
echo "Gitea is ready"
break
fi
sleep 2
done
curl -sf http://localhost:3000/api/v1/version > /dev/null || { echo "Gitea failed to start"; exit 1; }
- name: Start Windmill
working-directory: ./backend
env:
DATABASE_URL: postgres://postgres:changeme@localhost:5432/windmill
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
DENO_PATH: deno
BUN_PATH: bun
NODE_BIN_PATH: node
run: |
./target/debug/windmill &
echo "Waiting for Windmill to be ready..."
for i in $(seq 1 60); do
if curl -sf http://localhost:8000/api/version > /dev/null 2>&1; then
echo "Windmill is ready"
break
fi
sleep 2
done
curl -sf http://localhost:8000/api/version > /dev/null || { echo "Windmill failed to start"; exit 1; }
- name: Run git sync E2E tests
timeout-minutes: 10
env:
GITEA_DOCKER_URL: http://localhost:3000
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
run: |
python3 -m venv .venv
.venv/bin/pip install -r integration_tests/requirements.txt
cd integration_tests && ../.venv/bin/python -m unittest -v test.git_sync_test
- name: Archive logs
uses: actions/upload-artifact@v4
if: always()
with:
name: Git Sync Integration Tests Logs
path: |
integration_tests/logs

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubicloud-standard-8
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v6
- name: install_jq
run: |
sudo apt-get update

View File

@@ -14,7 +14,7 @@ jobs:
with:
node-version: "20.x"
registry-url: "https://registry.npmjs.org"
- run: cd typescript-client && ./publish.sh --access public && cd ..
- run: cd typescript-client && ./publish.sh && cd ..
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish_cli:
@@ -28,6 +28,6 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: cd cli && ./build.sh && cd npm && npm publish --access public
- run: cd cli && ./build.sh && cd npm && npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

1
.gitignore vendored
View File

@@ -27,4 +27,3 @@ typescript-client/node_modules
frontend/.svelte-kit
backend/chrome_profiler.json
.fast-check/
__pycache__/

View File

@@ -16,8 +16,7 @@ lifecycleHooks:
preRemove: bash ./scripts/pre-remove.sh
auto_name:
provider: claude
model: haiku
model: gemini-2.5-flash-lite
# Each service defines a port env var that webmux injects into pane and agent
# process environments when creating a worktree. Ports are auto-assigned:
@@ -33,7 +32,7 @@ services:
portStep: 10
profiles:
full:
default:
runtime: host
yolo: true
envPassthrough: []
@@ -45,9 +44,7 @@ profiles:
- Pane 2: frontend (npm run dev)
To check logs, use: \`tmux capture-pane -t .1 -p -S -50\` (backend) or \`tmux capture-pane -t .2 -p -S -50\` (frontend).
When restarting backend or frontend, make sure to use ${BACKEND_PORT} and ${FRONTEND_PORT}.
To connect to the database, use this connection string: ${DATABASE_URL}
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check.
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
panes:
- id: agent
kind: agent
@@ -55,51 +52,14 @@ profiles:
- id: backend
kind: command
split: right
command: ROOT="$(git rev-parse --show-toplevel)"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x "run ${CARGO_FEATURES:+--features $CARGO_FEATURES}"
command: ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x "run ${CARGO_FEATURES:+--features $CARGO_FEATURES}"
- id: frontend
kind: command
split: bottom
command: ROOT="$(git rev-parse --show-toplevel)"; cd "$ROOT/frontend" && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0
frontendOnly:
runtime: host
yolo: true
envPassthrough: []
systemPrompt: >
You are running inside a tmux session with other panes running services.
Pane layout (current window):
- Pane 0: this pane (claude agent)
- Pane 1: frontend (npm run dev)
To check logs, use: \`tmux capture-pane -t .1 -p -S -50\` (frontend).
When restarting frontend, make sure to use ${FRONTEND_PORT}.
To connect to the database, use this connection string: ${DATABASE_URL}
Because we are running frontend with npm run dev, to verify your changes, just check the logs in the frontend pane. No need for npm run build.
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
panes:
- id: agent
kind: agent
focus: true
- id: frontend
kind: command
split: right
command: ROOT="$(git rev-parse --show-toplevel)"; cd "$ROOT/frontend" && npm run generate-backend-client && npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0
agentOnly:
runtime: host
yolo: true
envPassthrough: []
systemPrompt: >
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
panes:
- id: agent
kind: agent
focus: true
command: ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/frontend" && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0
integrations:
github:
linkedRepos:
- repo: windmill-labs/windmill-ee-private
alias: ee-private
dir: ../windmill-ee-private__worktrees
linkedRepos: []
linear:
enabled: true

View File

@@ -67,7 +67,6 @@ files:
copy:
- backend/.env
- scripts/
- wm-ts-nav/target/release/wm-ts-nav
sandbox:
enabled: false

View File

@@ -1,98 +1,5 @@
# Changelog
## [1.657.2](https://github.com/windmill-labs/windmill/compare/v1.657.1...v1.657.2) (2026-03-15)
### Bug Fixes
* **cli:** Fix nonDottedPaths handling in cli flow lock generation ([#8375](https://github.com/windmill-labs/windmill/issues/8375)) ([eb03ebb](https://github.com/windmill-labs/windmill/commit/eb03ebbb0486b33c290fba3c34ea959e6e82fd13))
## [1.657.1](https://github.com/windmill-labs/windmill/compare/v1.657.0...v1.657.1) (2026-03-14)
### Bug Fixes
* powershell WindmillClient module loading on Windows workers ([#8370](https://github.com/windmill-labs/windmill/issues/8370)) ([3a268a9](https://github.com/windmill-labs/windmill/commit/3a268a9cf16add2ea2530e6eab247120a4d4754e))
## [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)
### Features
* add auto_commit option to Kafka triggers with advanced UI badges ([#8317](https://github.com/windmill-labs/windmill/issues/8317)) ([ec20d76](https://github.com/windmill-labs/windmill/commit/ec20d76216492086842c4f5e4e3b36727a5631e9))
* partition audit log table by day with configurable retention ([#8292](https://github.com/windmill-labs/windmill/issues/8292)) ([2aef01d](https://github.com/windmill-labs/windmill/commit/2aef01d18c0723aedcc626f4f3991195620774ab))
* support minimal telemetry mode ([#8243](https://github.com/windmill-labs/windmill/issues/8243)) ([fe1519f](https://github.com/windmill-labs/windmill/commit/fe1519f1284aadd67d5dce46cf0cb52ab351f789))
### Bug Fixes
* **cli:** instruct agent to tell user about generate-metadata and sync push instead of running them ([#8318](https://github.com/windmill-labs/windmill/issues/8318)) ([7fb729c](https://github.com/windmill-labs/windmill/commit/7fb729cc8483a2e6966a8e8995678929f4d451a0))
* fix saved inputs popover infinite loop ([#8311](https://github.com/windmill-labs/windmill/issues/8311)) ([425a75e](https://github.com/windmill-labs/windmill/commit/425a75e030b15fe65676169f9069fbb7da19828e))
* native mode now properly sets DB pool size and sleep queue ([#8332](https://github.com/windmill-labs/windmill/issues/8332)) ([d8b4132](https://github.com/windmill-labs/windmill/commit/d8b4132b9ae90af759c6655f4f69479f6738e60a))
* prevent zombie jobs from looping forever ([#8313](https://github.com/windmill-labs/windmill/issues/8313)) ([48bc3e2](https://github.com/windmill-labs/windmill/commit/48bc3e244558dccb1f08f455b299600861788b0d))
* set min_connections(0) to prevent sqlx pool spin loop ([#8334](https://github.com/windmill-labs/windmill/issues/8334)) ([bf4340f](https://github.com/windmill-labs/windmill/commit/bf4340f40c1eb9cacee4c32e07ba44f2c92bf7c4))
* show diff editor content for resources without a language ([#8331](https://github.com/windmill-labs/windmill/issues/8331)) ([cbc7e78](https://github.com/windmill-labs/windmill/commit/cbc7e78f8a60bff1d8730a6183cdbc9125d8e2b1))
* skip python preinstall on native workers ([#8329](https://github.com/windmill-labs/windmill/issues/8329)) ([4306c9e](https://github.com/windmill-labs/windmill/commit/4306c9e4fef317e298a76924edb4f20aa7ced105))
* skip token expiry notifications for debugger and mcp-oauth tokens ([#8316](https://github.com/windmill-labs/windmill/issues/8316)) ([8667329](https://github.com/windmill-labs/windmill/commit/86673291100fd16aaf216ed33ca9b648b8a2b7a5))
* use !inline ref for scripts inside flows (preproc, error, ai tool) ([#8319](https://github.com/windmill-labs/windmill/issues/8319)) ([ca8a627](https://github.com/windmill-labs/windmill/commit/ca8a6274bc81ad49fa0c6166694ae4d65a4048cb))
## [1.654.0](https://github.com/windmill-labs/windmill/compare/v1.653.0...v1.654.0) (2026-03-10)
### Features
* add git sync support for workspace dependencies ([#8144](https://github.com/windmill-labs/windmill/issues/8144)) ([4f29e05](https://github.com/windmill-labs/windmill/commit/4f29e05e3ae725e0be7ab797f8fa2186d8c5c0a5))
* add kafka trigger offset reset and auto.offset.reset config ([#8283](https://github.com/windmill-labs/windmill/issues/8283)) ([b02f9e5](https://github.com/windmill-labs/windmill/commit/b02f9e5c2426bff2356e1aaaa18e05b18c5efc6b))
* add preprocessor support for dedicated workers and bunnative scripts ([#8284](https://github.com/windmill-labs/windmill/issues/8284)) ([dc0e59f](https://github.com/windmill-labs/windmill/commit/dc0e59f432a0e3a53606adb8ac76d2dd2d365ace))
* add Vertex AI support for Google Gemini models ([#8303](https://github.com/windmill-labs/windmill/issues/8303)) ([cb349cb](https://github.com/windmill-labs/windmill/commit/cb349cb3d1b7561fb70a8c23fa83dc1c9441821c))
* **frontend:** replace flat sugiyama with recursive compound layout for flow graph ([#8204](https://github.com/windmill-labs/windmill/issues/8204)) ([cad4436](https://github.com/windmill-labs/windmill/commit/cad44365ac17029a2257f12cef061219b0265570))
### Bug Fixes
* **cli:** fail when passing an invalid --workspace arg ([#8294](https://github.com/windmill-labs/windmill/issues/8294)) ([f291b1c](https://github.com/windmill-labs/windmill/commit/f291b1cc19689e69e7aa008c19ce747e9c56240e))
* debounce webhook arg accumulation with max_count/max_time limits ([#8307](https://github.com/windmill-labs/windmill/issues/8307)) ([83be59e](https://github.com/windmill-labs/windmill/commit/83be59e0e866ebd091f1e27c0571710a989fd2e4))
* delete debounce_key on post-preprocessing limit exceeded ([#8299](https://github.com/windmill-labs/windmill/issues/8299)) ([438f609](https://github.com/windmill-labs/windmill/commit/438f609a78325ee5c2493079ca27bf587fa0d5ff))
* explicilty fail when --base-url --token --workspace are invalid ([#8302](https://github.com/windmill-labs/windmill/issues/8302)) ([5baeb8c](https://github.com/windmill-labs/windmill/commit/5baeb8c842a392c21457b7561e30b385e02a6a48))
* handle missing schema in RunnableByPath during wmill.d.ts generation ([#8300](https://github.com/windmill-labs/windmill/issues/8300)) ([b841e0a](https://github.com/windmill-labs/windmill/commit/b841e0a0384941079f37374f8fbbe2dd7fb51897))
* optimize flow lock generation and add rt.d.ts guidance for TS resource types ([#8295](https://github.com/windmill-labs/windmill/issues/8295)) ([b40cf80](https://github.com/windmill-labs/windmill/commit/b40cf80fdd62cbc31db0872ada551ce213b9dac8))
* preserve teams oauth tenant on settings page reload ([#8308](https://github.com/windmill-labs/windmill/issues/8308)) ([dbfa271](https://github.com/windmill-labs/windmill/commit/dbfa271b8962fe7b3d2aa8bf494e9557047fc8b3))
* resync custom_instance_user password on startup ([#8297](https://github.com/windmill-labs/windmill/issues/8297)) ([53ac43f](https://github.com/windmill-labs/windmill/commit/53ac43f5ee34570a9bb7b3441c73095e23690300))
* show meaningful error messages in database manager schema fetch ([#8296](https://github.com/windmill-labs/windmill/issues/8296)) ([cda8439](https://github.com/windmill-labs/windmill/commit/cda843922dcfd9a02ef9926751cbf8f544d2d4b6))
* skip loading flow preview history for new flows ([#8293](https://github.com/windmill-labs/windmill/issues/8293)) ([ac8c668](https://github.com/windmill-labs/windmill/commit/ac8c668cb93e56bc2a247bbdbbec14e5608125d2))
* teams selection not sticking in workspace settings ([#8309](https://github.com/windmill-labs/windmill/issues/8309)) ([fefc8c6](https://github.com/windmill-labs/windmill/commit/fefc8c62a00fe7a39f3104091e08087cd7c37afb))
## [1.653.0](https://github.com/windmill-labs/windmill/compare/v1.652.0...v1.653.0) (2026-03-10)

View File

@@ -4,7 +4,7 @@ Open-source platform for internal tools, workflows, API integrations, background
## Workflow
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.
1. **Understand**: Before coding, read relevant docs from `docs/` to understand the area you're changing
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`
@@ -14,8 +14,7 @@ Open-source platform for internal tools, workflows, API integrations, background
- **Validation**: `docs/validation.md` — what checks to run based on what you changed
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
- **Backend patterns**: use the `rust-backend` skill when writing Rust code
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code. Do NOT edit svelte files unless you have read that skill.
- **Code review**: use `/local-review` to review a PR for bugs and CLAUDE.md compliance
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code
- **Domain guides**: `.claude/skills/native-trigger/` and `frontend/tutorial-system-guide.mdc`
- **Brand/UI guidelines**: `frontend/brand-guidelines.md`
@@ -34,7 +33,6 @@ Open-source platform for internal tools, workflows, API integrations, background
Using `$bindable(default_value)` on props that can be `undefined` is **banned**. This pattern causes subtle bugs because the default value masks the `undefined` state.
**Bad:**
```svelte
let { my_prop = $bindable(default_value) }: { my_prop?: string } = $props()
```
@@ -42,7 +40,6 @@ let { my_prop = $bindable(default_value) }: { my_prop?: string } = $props()
**Correct alternatives:**
1. **Use `$derived` with nullish coalescing** — handle the potential `undefined` at the usage site:
```svelte
let { my_prop = $bindable() }: { my_prop?: string } = $props()
let effective_value = $derived(my_prop ?? default_value)
@@ -50,35 +47,8 @@ let { my_prop = $bindable(default_value) }: { my_prop?: string } = $props()
2. **Create a `useMyPropState()` helper** — encapsulate the undefined-handling logic in a reusable function and call it higher in the component tree, so the child component always receives a defined value.
## Code Navigation
`wm-ts-nav` is an AST-aware code navigator. Use **wm-ts-nav** for structural queries — it skips comments/strings and understands symbol boundaries.
**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
```bash
NAV="sh wm-ts-nav/nav"
# Use --root backend for Rust, --root frontend/src for TS/Svelte
$NAV --root backend outline backend/path/to/file.rs # file structure
$NAV --root backend def "ServiceName" # find definition
$NAV --root backend body "decrypt_oauth_data" # extract source code
$NAV --root backend search "%" --parent ServiceName # methods on a type
$NAV --root backend search "Trigger" --kind struct # find by kind
$NAV --root backend refs "X" --file handler.rs --caller # scoped refs with caller
$NAV --root backend callers "X" # who calls X?
$NAV --root backend callees "X" # what does X call?
```
**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
## Core Principles
- **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

View File

@@ -268,11 +268,11 @@ RUN bun install -g windmill-cli \
RUN curl -fsSL https://claude.ai/install.sh | bash \
&& cp /root/.local/share/claude/versions/* /usr/bin/claude
COPY --from=php:8.3.30-cli /usr/local/bin/php /usr/bin/php
COPY --from=composer:2.9.5 /usr/bin/composer /usr/bin/composer
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
# add the docker client to call docker from a worker if enabled
COPY --from=docker:29-dind /usr/local/bin/docker /usr/local/bin/
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
ENV RUSTUP_HOME="/tmp/windmill/cache/rustup"
ENV CARGO_HOME="/tmp/windmill/cache/cargo"

View File

@@ -192,6 +192,70 @@ sandbox:
This mounts both the main EE repo (used by the main worktree) and the EE worktrees directory (used by feature worktrees) into every sandbox container.
## Cursor SSH Integration (`wmc`)
`wm-cursor` (aliased as `wmc`) gives each worktree its own Cursor SSH remote window with an independently-focused tmux session. All windows are visible in the status bar across all Cursor terminals, but each one is focused on its own worktree.
This uses **grouped tmux sessions** — multiple sessions that share the same window list but track focus independently:
```
tmux session: main <-- your main Cursor terminal
tmux session: cursor-feat-a <-- Cursor window for feat-a (focused on wm-feat-a)
tmux session: cursor-feat-b <-- Cursor window for feat-b (focused on wm-feat-b)
\__ all three share the same windows in the status bar
```
### Setup
Run once from inside tmux on the remote:
```bash
./scripts/wm-cursor setup /home/hugo/projects/windmill
```
This:
1. **Merges `.vscode/settings.json`** — adds the `wm-tmux` terminal profile (auto-attaches to the `main` tmux session), disables auto port forwarding, configures forwarding for ports 8000/3000/5432, and stops rust-analyzer from auto-starting. Existing settings are preserved.
2. **Creates `.vscode/tasks.json`** — auto-starts the dev database (`start-dev-db.sh`) when the folder opens.
3. **Adds `wmc` alias to `~/.zshrc`** — so you can use `wmc` from any tmux window.
4. **Adds `eval "$(wmc completions)"`** to `~/.zshrc` — provides tab-completion for subcommands and worktree names (for `open`, `open-ee`, and `close`).
After setup, reopen Cursor's terminal to pick up the new profile.
### Usage
All commands run from inside a tmux session (i.e., from Cursor's integrated terminal after setup).
**Create a new worktree + open Cursor:**
```bash
wmc add -A -p "implement feature X"
```
This runs `workmux add`, creates a grouped tmux session, writes `.vscode/settings.json` in the worktree (with port forwarding matching the worktree's assigned ports), and opens a new Cursor window.
**Open Cursor for an existing worktree:**
```bash
wmc open my-feature
```
**Open the EE worktree in Cursor (no tmux session):**
```bash
wmc open-ee my-feature
```
This finds the matching `windmill-ee-private__worktrees/<name>` directory and opens it in a new Cursor window.
**Close a worktree's Cursor window and tmux window (keeps the worktree):**
```bash
wmc close my-feature
```
This kills the grouped tmux session and calls `workmux close` to close the tmux window. The worktree and branch are preserved. Grouped sessions are also automatically cleaned up when you `workmux rm` a worktree (via `scripts/worktree-cleanup`).
## Cargo Features
To build the backend with specific Cargo features (e.g., `enterprise`, `parquet`), pass them via `CARGO_FEATURES`. The backend pane reads this from `.env.local` and appends `--features <value>` to the `cargo watch` command.
@@ -206,6 +270,20 @@ CARGO_FEATURES="enterprise,parquet" wm add my-feature
This gets written to `.env.local` by the `post_create` hook (`scripts/worktree-env`), and the backend pane picks it up automatically.
**With `wmc` (wm-cursor):**
Use the `--features` flag:
```bash
# Create a new worktree with features
wmc add --features "enterprise,parquet" -A -p "implement feature X"
# Open an existing worktree with different features
wmc open my-feature --features "enterprise,parquet"
```
The `--features` flag exports `CARGO_FEATURES` so the `post_create` hook writes it to `.env.local`. When using `wmc open`, it updates the existing `.env.local` with the new features.
## Login
Default credentials: `admin@windmill.dev` / `changeme`

View File

@@ -1,41 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, topic, partition, \"offset\" FROM kafka_pending_commits\n WHERE workspace_id = $1 AND kafka_trigger_path = $2\n ORDER BY id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "topic",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "partition",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "offset",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "038d2fde90fa9e99e30d15161777fa3ab402e33edfca46daa95b52e525424586"
}

View File

@@ -46,11 +46,11 @@
]
},
"nullable": [
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
true,
true
]

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id,\n elem->>'account_id' as account_id,\n elem->>'github_base_url' as github_base_url\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ",
"query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id,\n elem->>'account_id' as account_id\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ",
"describe": {
"columns": [
{
@@ -12,11 +12,6 @@
"ordinal": 1,
"name": "account_id",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "github_base_url",
"type_info": "Text"
}
],
"parameters": {
@@ -25,10 +20,9 @@
]
},
"nullable": [
null,
null,
null
]
},
"hash": "ae7adc583cdd3f876164ed60569ed531b05eaa17fccc599306eb1a96a65ee761"
"hash": "0ee14619dd81df460b2b8cc6df2b89646279f77469c35deffca8e17a11d7f6c8"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE kafka_trigger\n SET\n kafka_resource_path = $1,\n group_id = $2,\n topics = $3,\n filters = $4,\n auto_offset_reset = $5,\n auto_commit = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n edited_by = $10,\n email = $11,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND path = $13\n ",
"query": "\n UPDATE kafka_trigger\n SET\n kafka_resource_path = $1,\n group_id = $2,\n topics = $3,\n filters = $4,\n auto_offset_reset = $5,\n script_path = $6,\n path = $7,\n is_flow = $8,\n edited_by = $9,\n email = $10,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $13,\n error_handler_args = $14,\n retry = $15\n WHERE\n workspace_id = $11 AND path = $12\n ",
"describe": {
"columns": [],
"parameters": {
@@ -10,7 +10,6 @@
"VarcharArray",
"JsonbArray",
"Varchar",
"Bool",
"Varchar",
"Varchar",
"Bool",
@@ -25,5 +24,5 @@
},
"nullable": []
},
"hash": "072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04"
"hash": "12631fecee6aa11a45cf5c8d101c0dd8de50ac9b57e68198f637038d344fdd46"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT reset_offset FROM kafka_trigger WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "reset_offset",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "1df610a583e86edb70c374fd66c68554a6a4291426c09dd5b04fd832f9d31208"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT EXISTS(SELECT 1 FROM v2_job_queue WHERE id = $1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
null
]
},
"hash": "2c8b8ed14647332491846ae3fa8b0ab8113d52ae8ae613a810c2b452e0972d05"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM variable WHERE path = $1 AND workspace_id = $2 RETURNING path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "3317484a9c09c07c2c9db9debaecc4a4d518093ab48e79365dbb808068e0b8ff"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT (config->>'native_mode')::boolean FROM config WHERE name = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "bool",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "36b95bc7956eb7bba7cd6fa9cd829980a0bf4970b919cabad1daab16627404fc"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, email, auto_commit\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"VarcharArray",
"Varchar",
"Varchar",
"Bool",
"Varchar",
"Varchar",
"Varchar",
"Bool"
]
},
"nullable": []
},
"hash": "45fc21026fa76e5d69f00a68a7be81abb3ec627578f2d14f0ce33896dc6ab4cf"
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET git_app_installations = (\n SELECT jsonb_agg(\n CASE\n WHEN (elem->>'installation_id')::bigint = $2 THEN $1::jsonb\n ELSE elem\n END\n )\n FROM jsonb_array_elements(git_app_installations) AS elem\n )\n WHERE workspace_id = $3\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "48b394bd9ca63d33a7ea97113b0096bd0777da52c05e23262572089e0c3c6c46"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT auto_commit FROM kafka_trigger WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "auto_commit",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "4b2a29b3ef7ec4802d81ec4b706623b991c938e40d0db25290b03dc0577c2740"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO kafka_trigger (\n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n filters,\n auto_offset_reset,\n auto_commit,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, now(), $14, $15, $16\n )\n ",
"query": "\n INSERT INTO kafka_trigger (\n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n filters,\n auto_offset_reset,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), $13, $14, $15\n )\n ",
"describe": {
"columns": [],
"parameters": {
@@ -12,7 +12,6 @@
"VarcharArray",
"JsonbArray",
"Varchar",
"Bool",
"Varchar",
"Bool",
{
@@ -36,5 +35,5 @@
},
"nullable": []
},
"hash": "5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f"
"hash": "4b5a711986017654bdd495893a16ddc6ab09c98cd8723865cbd341404bc6a02f"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT auto_commit\n FROM kafka_trigger\n WHERE workspace_id = $1 AND path = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "auto_commit",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "50807b807bb901a380926798be655c13a18dfd26e237a8218d3006e2898b5aa3"
}

View File

@@ -15,7 +15,7 @@
]
},
"nullable": [
null
true
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "WITH active_users AS (SELECT distinct username as email FROM (SELECT username, timestamp, operation FROM audit_partitioned UNION ALL SELECT username, timestamp, operation FROM audit) AS a WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username, first_time_user\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC, devops DESC\n LIMIT $1 OFFSET $2",
"query": "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username, first_time_user\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC, devops DESC\n LIMIT $1 OFFSET $2",
"describe": {
"columns": [
{
@@ -73,5 +73,5 @@
false
]
},
"hash": "9229d9a9ff389cf26e480b604b83900e2d362ee934ef27284ef39f4eed440e59"
"hash": "72d3ebb05ac1ffeb0e8d0a3146d95bb5b90e7c4d1dc2c8a6ef06eddf6678f230"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT kafka_resource_path, topics, group_id, mode AS \"mode: String\",\n auto_offset_reset, auto_commit, reset_offset\n FROM kafka_trigger\n WHERE workspace_id = $1 AND path = $2\n ",
"query": "\n SELECT kafka_resource_path, topics, group_id, mode AS \"mode: String\"\n FROM kafka_trigger\n WHERE workspace_id = $1 AND path = $2\n ",
"describe": {
"columns": [
{
@@ -33,21 +33,6 @@
}
}
}
},
{
"ordinal": 4,
"name": "auto_offset_reset",
"type_info": "Varchar"
},
{
"ordinal": 5,
"name": "auto_commit",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "reset_offset",
"type_info": "Bool"
}
],
"parameters": {
@@ -57,14 +42,11 @@
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "4cf4be7a981173d3f242887d9313c7e60d23e9827f23c0de5b546ed56697d54a"
"hash": "7e3bfb33fb771aec39b43a7550091ce7c9b1261b52d10f4a7f3273fed3c916df"
}

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM kafka_pending_commits WHERE id = ANY($1)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8Array"
]
},
"nullable": []
},
"hash": "80bad96cbec6b5eca57a6380e7515565490a271050dcc4b5aac2b730ae3a55b9"
}

View File

@@ -1,28 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id,\n elem->>'github_base_url' as github_base_url\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "installation_id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "github_base_url",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null,
null
]
},
"hash": "ad5fc9212a123a8328397496ef3b5eea1780226698e419ac59ba55012296913d"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO audit_partitioned\n (workspace_id, username, operation, action_kind, resource, parameters, email, span)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
"query": "INSERT INTO audit\n (workspace_id, username, operation, action_kind, resource, parameters, email, span)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
"describe": {
"columns": [],
"parameters": {
@@ -29,5 +29,5 @@
},
"nullable": []
},
"hash": "fbccafe6d34093a723b9d5a6ee8d618a80ceba6de2d39202e6293ef5207c31f6"
"hash": "ad8487a797713b3a6c10fb399c9fb8dcd940bb92e998145e250f28ccfe1c7033"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET git_app_installations = (\n SELECT jsonb_agg(\n CASE\n WHEN (elem->>'installation_id')::bigint = $2 THEN $1::jsonb\n ELSE elem\n END\n )\n FROM jsonb_array_elements(git_app_installations) AS elem\n )\n WHERE workspace_id = $3\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "b4d48c820bf41619bffa8f62367e98369e1d93514e1618723a34bf96080d4ebc"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "installation_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "be00ac55e8668a0ed3befda7d8595c7cda0cba0b119d4fdb8a0dea1b28a1d560"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_completed\n (workspace_id, id, started_at, duration_ms, result, memory_peak, status, worker)\n SELECT q.workspace_id, q.id, q.started_at,\n COALESCE((EXTRACT('epoch' FROM now()) - EXTRACT('epoch' FROM COALESCE(q.started_at, now()))) * 1000, 0)::bigint,\n $2::jsonb, r.memory_peak, 'failure'::job_status, q.worker\n FROM v2_job_queue q\n LEFT JOIN v2_job_runtime r ON r.id = q.id\n WHERE q.id = $1\n ON CONFLICT (id) DO UPDATE SET status = 'failure', result = $2::jsonb",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Jsonb"
]
},
"nullable": []
},
"hash": "beecb176df512e4a94771d0d73c4c597e07e53d499131b57e4d6441fd0af09cb"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM resource WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "bf2aeb9a1e649106d2a084c1d628690a44573c1869a206474811215714ba97c2"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE kafka_trigger SET reset_offset = true, server_id = NULL WHERE workspace_id = $1 AND path = $2 RETURNING true",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "c2f38c9e09aac73d10e8f327715927c07832badb2c9145d5996b829163bdf7d9"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE kafka_trigger SET reset_offset = false WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "ef15599f532fab2cbb487542ffec047cf3b7ce22ce868db1b1a63e6c10d0d12b"
}

View File

@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO kafka_pending_commits (workspace_id, kafka_trigger_path, topic, partition, \"offset\")\n VALUES ($1, $2, $3, $4, $5)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Int4",
"Int8"
]
},
"nullable": []
},
"hash": "f67e5c96eb9cb35953d4c3e83e0fcbb5b647737e0366529a2f418218b1a74679"
}

267
backend/Cargo.lock generated
View File

@@ -169,9 +169,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "1.0.0"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -184,15 +184,15 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.14"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
@@ -1860,9 +1860,9 @@ dependencies = [
[[package]]
name = "bon"
version = "3.9.1"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe"
checksum = "2d13a61f2963b88eef9c1be03df65d42f6996dfeac1054870d950fcf66686f83"
dependencies = [
"bon-macros",
"rustversion",
@@ -1870,9 +1870,9 @@ dependencies = [
[[package]]
name = "bon-macros"
version = "3.9.1"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c"
checksum = "d314cc62af2b6b0c65780555abb4d02a03dd3b799cd42419044f0c38d99738c0"
dependencies = [
"darling 0.23.0",
"ident_case",
@@ -2208,9 +2208,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.57"
version = "1.2.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -2323,9 +2323,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.6.0"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
dependencies = [
"clap_builder",
"clap_derive",
@@ -2333,9 +2333,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.6.0"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [
"anstream",
"anstyle",
@@ -2345,9 +2345,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.6.0"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -2357,9 +2357,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "1.1.0"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "clipboard-win"
@@ -2418,9 +2418,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.5"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "combine"
@@ -8208,9 +8208,9 @@ dependencies = [
[[package]]
name = "lz4_flex"
version = "0.11.6"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a"
checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
dependencies = [
"twox-hash 2.1.2",
]
@@ -9383,9 +9383,9 @@ checksum = "80adb31078122c880307e9cdfd4e3361e6545c319f9b9dcafcb03acd3b51a575"
[[package]]
name = "once_cell"
version = "1.21.4"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
@@ -9460,9 +9460,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.76"
version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [
"bitflags 2.9.4",
"cfg-if",
@@ -9507,9 +9507,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.112"
version = "0.9.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [
"cc",
"libc",
@@ -10641,9 +10641,9 @@ dependencies = [
[[package]]
name = "quick_cache"
version = "0.6.19"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "530e84778a55de0f52645a51d4e3b9554978acd6a1e7cd50b6a6784692b3029e"
checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3"
dependencies = [
"ahash 0.8.12",
"equivalent",
@@ -11987,9 +11987,9 @@ checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
[[package]]
name = "schannel"
version = "0.1.29"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
@@ -13854,9 +13854,9 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.27.0"
version = "3.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [
"fastrand",
"getrandom 0.4.2",
@@ -14087,9 +14087,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.11.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
dependencies = [
"tinyvec_macros",
]
@@ -14697,9 +14697,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.23"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"matchers",
"nu-ansi-term",
@@ -15741,7 +15741,7 @@ dependencies = [
[[package]]
name = "windmill"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-nats",
@@ -15808,7 +15808,7 @@ dependencies = [
[[package]]
name = "windmill-alerting"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -15821,7 +15821,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"argon2",
@@ -15941,7 +15941,6 @@ dependencies = [
"windmill-parser-py",
"windmill-parser-py-imports",
"windmill-parser-sql",
"windmill-parser-sql-asset",
"windmill-parser-ts",
"windmill-queue",
"windmill-store",
@@ -15962,7 +15961,7 @@ dependencies = [
[[package]]
name = "windmill-api-agent-workers"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -15985,7 +15984,7 @@ dependencies = [
[[package]]
name = "windmill-api-assets"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -15998,7 +15997,7 @@ dependencies = [
[[package]]
name = "windmill-api-auth"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16024,7 +16023,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"reqwest 0.12.28",
"serde",
@@ -16034,7 +16033,7 @@ dependencies = [
[[package]]
name = "windmill-api-configs"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16051,7 +16050,7 @@ dependencies = [
[[package]]
name = "windmill-api-debug"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"base64 0.22.1",
@@ -16074,7 +16073,7 @@ dependencies = [
[[package]]
name = "windmill-api-embeddings"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16097,7 +16096,7 @@ dependencies = [
[[package]]
name = "windmill-api-flow-conversations"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16113,7 +16112,7 @@ dependencies = [
[[package]]
name = "windmill-api-flows"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16133,7 +16132,7 @@ dependencies = [
[[package]]
name = "windmill-api-groups"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16153,7 +16152,7 @@ dependencies = [
[[package]]
name = "windmill-api-inputs"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16167,7 +16166,7 @@ dependencies = [
[[package]]
name = "windmill-api-integration-tests"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-nats",
@@ -16187,7 +16186,6 @@ dependencies = [
"windmill-api-auth",
"windmill-api-client",
"windmill-common",
"windmill-git-sync",
"windmill-native-triggers",
"windmill-test-utils",
"windmill-worker",
@@ -16195,7 +16193,7 @@ dependencies = [
[[package]]
name = "windmill-api-jobs"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16220,7 +16218,7 @@ dependencies = [
[[package]]
name = "windmill-api-npm-proxy"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"flate2",
@@ -16238,7 +16236,7 @@ dependencies = [
[[package]]
name = "windmill-api-openapi"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16259,7 +16257,7 @@ dependencies = [
[[package]]
name = "windmill-api-schedule"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16279,7 +16277,7 @@ dependencies = [
[[package]]
name = "windmill-api-scripts"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16309,7 +16307,7 @@ dependencies = [
[[package]]
name = "windmill-api-settings"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16336,7 +16334,7 @@ dependencies = [
[[package]]
name = "windmill-api-sse"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"lazy_static",
"serde",
@@ -16348,7 +16346,7 @@ dependencies = [
[[package]]
name = "windmill-api-users"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"argon2",
"axum 0.7.9",
@@ -16371,7 +16369,7 @@ dependencies = [
[[package]]
name = "windmill-api-workers"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16385,7 +16383,7 @@ dependencies = [
[[package]]
name = "windmill-api-workspaces"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16416,7 +16414,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"chrono",
"lazy_static",
@@ -16430,7 +16428,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16449,7 +16447,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"aes-gcm",
"anyhow",
@@ -16548,7 +16546,7 @@ dependencies = [
[[package]]
name = "windmill-dep-map"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"chrono",
"itertools 0.14.0",
@@ -16567,7 +16565,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"regex",
"serde",
@@ -16582,7 +16580,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -16606,7 +16604,7 @@ dependencies = [
[[package]]
name = "windmill-jseval"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"futures",
@@ -16623,7 +16621,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -16639,7 +16637,7 @@ dependencies = [
[[package]]
name = "windmill-mcp"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16660,7 +16658,7 @@ dependencies = [
[[package]]
name = "windmill-native-triggers"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -16691,7 +16689,7 @@ dependencies = [
[[package]]
name = "windmill-oauth"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-oauth2",
@@ -16715,7 +16713,7 @@ dependencies = [
[[package]]
name = "windmill-object-store"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-stream",
@@ -16749,7 +16747,7 @@ dependencies = [
[[package]]
name = "windmill-operator"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"futures",
@@ -16767,7 +16765,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -16776,7 +16774,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16788,7 +16786,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"serde_json",
@@ -16800,7 +16798,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"gosyn",
@@ -16812,7 +16810,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16824,7 +16822,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"serde_json",
@@ -16836,7 +16834,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -16847,7 +16845,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16858,7 +16856,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16866,22 +16864,12 @@ dependencies = [
"rustpython-parser",
"serde_json",
"windmill-parser",
]
[[package]]
name = "windmill-parser-py-asset"
version = "1.657.2"
dependencies = [
"anyhow",
"rustpython-ast",
"rustpython-parser",
"windmill-parser",
"windmill-parser-sql-asset",
"windmill-parser-sql",
]
[[package]]
name = "windmill-parser-py-imports"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -16905,7 +16893,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16919,7 +16907,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -16936,7 +16924,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16944,17 +16932,6 @@ dependencies = [
"regex-lite",
"serde",
"serde_json",
"windmill-parser",
"windmill-types",
]
[[package]]
name = "windmill-parser-sql-asset"
version = "1.657.2"
dependencies = [
"anyhow",
"serde",
"serde_json",
"sqlparser 0.59.0",
"windmill-parser",
"windmill-types",
@@ -16962,7 +16939,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -16976,27 +16953,12 @@ dependencies = [
"triomphe",
"wasm-bindgen",
"windmill-parser",
]
[[package]]
name = "windmill-parser-ts-asset"
version = "1.657.2"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
"swc_common",
"swc_ecma_ast",
"swc_ecma_parser",
"swc_ecma_visit",
"triomphe",
"wasm-bindgen",
"windmill-parser",
"windmill-parser-sql-asset",
"windmill-parser-sql",
]
[[package]]
name = "windmill-parser-wac"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -17012,7 +16974,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"serde",
@@ -17023,7 +16985,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -17060,7 +17022,7 @@ dependencies = [
[[package]]
name = "windmill-runtime-nativets"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"const_format",
@@ -17098,7 +17060,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"getrandom 0.3.4",
"wasm-bindgen",
@@ -17109,7 +17071,7 @@ dependencies = [
[[package]]
name = "windmill-store"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -17138,7 +17100,7 @@ dependencies = [
[[package]]
name = "windmill-test-utils"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -17161,7 +17123,7 @@ dependencies = [
[[package]]
name = "windmill-trigger"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17194,7 +17156,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-email"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17214,7 +17176,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-gcp"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17248,7 +17210,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-http"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17283,7 +17245,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-kafka"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17306,7 +17268,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-mqtt"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17330,7 +17292,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-nats"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-nats",
@@ -17354,7 +17316,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-postgres"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17389,7 +17351,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-sqs"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17417,7 +17379,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-websocket"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-trait",
@@ -17440,7 +17402,7 @@ dependencies = [
[[package]]
name = "windmill-types"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"bitflags 2.9.4",
@@ -17458,7 +17420,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"anyhow",
"async-once-cell",
@@ -17521,7 +17483,6 @@ dependencies = [
"sha2 0.10.9",
"sqlx",
"tar",
"tempfile",
"tiberius",
"tokio",
"tokio-postgres 0.7.13",
@@ -17565,7 +17526,7 @@ dependencies = [
[[package]]
name = "windmill-worker-volumes"
version = "1.657.2"
version = "1.653.0"
dependencies = [
"bytes",
"futures",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.657.2"
version = "1.653.0"
authors.workspace = true
edition.workspace = true
@@ -59,7 +59,6 @@ members = [
"./windmill-oauth",
"./parsers/windmill-parser",
"./parsers/windmill-parser-ts",
"./parsers/windmill-parser-ts-asset",
"./parsers/windmill-parser-go",
"./parsers/windmill-parser-rust",
"./parsers/windmill-parser-csharp",
@@ -68,11 +67,8 @@ members = [
"./parsers/windmill-parser-ruby",
"./parsers/windmill-parser-bash",
"./parsers/windmill-parser-py",
"./parsers/windmill-parser-py-asset",
"./parsers/windmill-parser-py-imports",
"./parsers/windmill-parser-wac",
"./parsers/windmill-parser-sql",
"./parsers/windmill-parser-sql-asset",
"./parsers/windmill-sql-datatype-parser-wasm",
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu",
"./windmill-worker-volumes",
@@ -82,7 +78,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal"]
[workspace.package]
version = "1.657.2"
version = "1.653.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -324,9 +320,7 @@ windmill-api-workers = { path = "./windmill-api-workers" }
windmill-store = { path = "./windmill-store" }
windmill-parser = { path = "./parsers/windmill-parser" }
windmill-parser-ts = { path = "./parsers/windmill-parser-ts" }
windmill-parser-ts-asset = { path = "./parsers/windmill-parser-ts-asset" }
windmill-parser-py = { path = "./parsers/windmill-parser-py" }
windmill-parser-py-asset = { path = "./parsers/windmill-parser-py-asset" }
windmill-parser-py-imports = { path = "./parsers/windmill-parser-py-imports" }
windmill-parser-go = { path = "./parsers/windmill-parser-go" }
windmill-parser-rust = { path = "./parsers/windmill-parser-rust" }
@@ -337,7 +331,6 @@ windmill-parser-ruby = { path = "./parsers/windmill-parser-ruby" }
windmill-parser-nu = { path = "./parsers/windmill-parser-nu" }
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
windmill-parser-sql-asset = { path = "./parsers/windmill-parser-sql-asset" }
windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
windmill-parser-php = { path = "./parsers/windmill-parser-php" }
windmill-parser-wac = { path = "./parsers/windmill-parser-wac" }

View File

@@ -1 +1 @@
c74c86b78a66b976fd9968b21f77903723e668ec
f97defd916b982ef490d7ee69c671655bc9858b4

View File

@@ -1 +0,0 @@
DROP TABLE IF EXISTS audit_partitioned CASCADE;

View File

@@ -1,58 +0,0 @@
-- Create a new daily-partitioned audit table alongside the existing one.
-- New inserts go to audit_partitioned; reads UNION ALL both tables.
-- The old audit table empties out naturally via retention cleanup.
CREATE TABLE audit_partitioned (
workspace_id VARCHAR(50) NOT NULL,
id BIGINT NOT NULL DEFAULT nextval('audit_id_seq'),
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
username VARCHAR(255) NOT NULL,
operation VARCHAR(50) NOT NULL,
action_kind ACTION_KIND NOT NULL,
resource VARCHAR(255),
parameters JSONB,
email VARCHAR(255),
span VARCHAR(255),
PRIMARY KEY (id, timestamp)
) PARTITION BY RANGE (timestamp);
-- Create daily partitions for today + 3 days
DO $$
DECLARE
curr_date DATE := CURRENT_DATE;
end_date DATE := CURRENT_DATE + INTERVAL '3 days';
BEGIN
WHILE curr_date <= end_date LOOP
EXECUTE format(
'CREATE TABLE %I PARTITION OF audit_partitioned FOR VALUES FROM (%L) TO (%L)',
'audit_' || to_char(curr_date, 'YYYYMMDD'),
curr_date,
curr_date + INTERVAL '1 day'
);
curr_date := curr_date + INTERVAL '1 day';
END LOOP;
END $$;
-- Indexes (auto-propagated to all current and future partitions)
CREATE INDEX ix_audit_partitioned_timestamps ON audit_partitioned (timestamp DESC);
CREATE INDEX idx_audit_partitioned_workspace ON audit_partitioned (workspace_id, timestamp DESC);
CREATE INDEX idx_audit_partitioned_recent_login_activities
ON audit_partitioned (timestamp, username)
WHERE operation IN ('users.login', 'oauth.login', 'users.token.refresh');
-- Grants (match the old audit table)
GRANT ALL ON audit_partitioned TO windmill_user;
GRANT ALL ON audit_partitioned TO windmill_admin;
-- RLS (match the old audit table)
ALTER TABLE audit_partitioned ENABLE ROW LEVEL SECURITY;
CREATE POLICY admin_policy ON audit_partitioned FOR ALL TO windmill_admin USING (true);
CREATE POLICY see_own ON audit_partitioned FOR ALL TO windmill_user
USING ((username)::text = current_setting('session.user'::text));
CREATE POLICY schedule ON audit_partitioned FOR INSERT TO windmill_user
WITH CHECK ((username)::text ~~ 'schedule-%'::text);
CREATE POLICY schedule_audit ON audit_partitioned FOR INSERT TO windmill_user
WITH CHECK ((parameters ->> 'end_user'::text) ~~ 'schedule-%'::text);
CREATE POLICY webhook ON audit_partitioned FOR INSERT TO windmill_user
WITH CHECK ((username)::text ~~ 'webhook-%'::text);

View File

@@ -1,2 +0,0 @@
DROP TABLE IF EXISTS kafka_pending_commits;
ALTER TABLE kafka_trigger DROP COLUMN auto_commit;

View File

@@ -1,14 +0,0 @@
ALTER TABLE kafka_trigger ADD COLUMN auto_commit BOOLEAN NOT NULL DEFAULT TRUE;
CREATE TABLE kafka_pending_commits (
id BIGSERIAL PRIMARY KEY,
workspace_id VARCHAR(50) NOT NULL,
kafka_trigger_path VARCHAR(255) NOT NULL,
topic VARCHAR(255) NOT NULL,
partition INTEGER NOT NULL,
"offset" BIGINT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
FOREIGN KEY (workspace_id, kafka_trigger_path) REFERENCES kafka_trigger(workspace_id, path) ON DELETE CASCADE
);
CREATE INDEX idx_kafka_pending_commits_trigger ON kafka_pending_commits (workspace_id, kafka_trigger_path);

View File

@@ -1,16 +0,0 @@
[package]
name = "windmill-parser-py-asset"
version.workspace = true
edition.workspace = true
authors.workspace = true
[lib]
name = "windmill_parser_py_asset"
path = "./src/lib.rs"
[dependencies]
windmill-parser.workspace = true
windmill-parser-sql-asset.workspace = true
rustpython-parser.workspace = true
rustpython-ast = { version = "0.4.0", features = ["visitor"] }
anyhow.workspace = true

View File

@@ -10,6 +10,7 @@ path = "./src/lib.rs"
[dependencies]
windmill-parser.workspace = true
windmill-parser-sql.workspace = true
rustpython-parser.workspace = true
itertools.workspace = true
serde_json.workspace = true

View File

@@ -215,7 +215,7 @@ impl AssetsFinder {
_ => return Err(()),
};
// We use the SQL parser to detect RW, specific tables, etc.
let sql_assets = windmill_parser_sql_asset::parse_wmill_sdk_sql_assets(
let sql_assets = windmill_parser_sql::parse_wmill_sdk_sql_assets(
*kind,
path,
schema.as_deref(),

View File

@@ -21,54 +21,13 @@ use rustpython_parser::{
Parse,
};
pub mod asset_parser;
pub mod pydantic_parser;
pub use asset_parser::parse_assets;
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.
@@ -434,19 +393,8 @@ 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: union_otyp.or_else(|| {
metadata.descriptions.get(&arg_name).map(|d| d.to_string())
}),
otyp: metadata.descriptions.get(&arg_name).map(|d| d.to_string()),
name: arg_name,
typ,
has_default: has_default || default.is_some(),
@@ -496,9 +444,6 @@ 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)
}
@@ -1104,33 +1049,6 @@ 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#"

View File

@@ -1,17 +0,0 @@
[package]
name = "windmill-parser-sql-asset"
version.workspace = true
edition.workspace = true
authors.workspace = true
[lib]
name = "windmill_parser_sql_asset"
path = "./src/lib.rs"
[dependencies]
windmill-parser.workspace = true
windmill-types.workspace = true
anyhow.workspace = true
serde_json.workspace = true
serde.workspace = true
sqlparser = { version = "0.59.0", features = ["visitor"] }

View File

@@ -1,4 +0,0 @@
mod asset_parser;
mod asset_parser_utils;
pub use asset_parser::parse_assets;
pub use asset_parser_utils::parse_wmill_sdk_sql_assets;

View File

@@ -20,4 +20,5 @@ windmill-types.workspace = true
anyhow.workspace = true
lazy_static.workspace = true
serde_json.workspace = true
serde.workspace = true
serde.workspace = true
sqlparser = { version = "0.59.0", features = ["visitor"] }

View File

@@ -20,6 +20,11 @@ pub use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ};
pub const SANITIZED_ENUM_STR: &str = "__sanitized_enum__";
pub const SANITIZED_RAW_STRING_STR: &str = "__sanitized_raw_string__";
mod asset_parser;
mod asset_parser_utils;
pub use asset_parser::parse_assets;
pub use asset_parser_utils::parse_wmill_sdk_sql_assets;
pub fn parse_mysql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_mysql_file(&code)?;
if let Some(x) = parsed {

View File

@@ -1,23 +0,0 @@
[package]
name = "windmill-parser-ts-asset"
version.workspace = true
edition.workspace = true
authors.workspace = true
[lib]
name = "windmill_parser_ts_asset"
path = "./src/lib.rs"
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen.workspace = true
serde-wasm-bindgen.workspace = true
[dependencies]
windmill-parser.workspace = true
windmill-parser-sql-asset.workspace = true
swc_common.workspace = true
triomphe.workspace = true
swc_ecma_parser.workspace = true
swc_ecma_ast.workspace = true
swc_ecma_visit.workspace = true
anyhow.workspace = true

View File

@@ -15,6 +15,7 @@ serde-wasm-bindgen.workspace = true
[dependencies]
windmill-parser.workspace = true
windmill-parser-sql.workspace = true
swc_common.workspace = true
triomphe.workspace = true
swc_ecma_parser.workspace = true

View File

@@ -259,7 +259,7 @@ impl Visit for AssetsFinder {
});
// We use the SQL parser to detect RW, specific tables, etc.
let sql_assets = windmill_parser_sql_asset::parse_wmill_sdk_sql_assets(
let sql_assets = windmill_parser_sql::parse_wmill_sdk_sql_assets(
*kind,
asset_name,
schema.as_deref(),

View File

@@ -211,6 +211,8 @@ pub enum TypeDecl {
Interface(TsInterfaceDecl),
Alias(TsTypeAliasDecl),
}
pub mod asset_parser;
pub use asset_parser::parse_assets;
/// skip_params is a micro optimization for when we just want to find the main
/// function without parsing all the params.
@@ -363,12 +365,8 @@ 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,
otyp: None,
name,
typ,
default: None,
@@ -378,21 +376,13 @@ fn parse_param(
}
// Pat::Object(ObjectPat { ... }) = todo!()
Pat::Assign(AssignPat { left, right, .. }) => {
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)
}
let (name, mut typ, _nullable) = match *left {
Pat::Ident(ident) => binding_ident_to_arg(symbol_table, type_resolver, &ident),
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, None)
(name, typ, nullable)
}
_ => {
return Err(anyhow::anyhow!(
@@ -428,7 +418,7 @@ fn parse_param(
if typ == Typ::Unknown && dflt.is_some() {
typ = json_to_typ(dflt.as_ref().unwrap(), false);
}
Ok(Arg { otyp, name, typ, default: dflt, has_default: true, oidx: None })
Ok(Arg { otyp: None, 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);
@@ -973,75 +963,6 @@ 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, .. }) => {

View File

@@ -646,55 +646,6 @@ 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#"

View File

@@ -39,7 +39,6 @@ nu-parser = [ "dep:windmill-parser-nu"]
java-parser = [ "dep:windmill-parser-java"]
ruby-parser = [ "dep:windmill-parser-ruby"]
wac-parser = [ "dep:windmill-parser-wac"]
asset-parser = [ "dep:windmill-parser-ts-asset", "dep:windmill-parser-py-asset", "dep:windmill-parser-sql-asset"]
[dependencies]
anyhow.workspace = true
@@ -58,9 +57,6 @@ windmill-parser-nu = { workspace = true, optional = true }
windmill-parser-java = { workspace = true, optional = true }
windmill-parser-ruby = { workspace = true, optional = true }
windmill-parser-wac = { workspace = true, optional = true }
windmill-parser-ts-asset = { workspace = true, optional = true }
windmill-parser-py-asset = { workspace = true, optional = true }
windmill-parser-sql-asset = { workspace = true, optional = true }
wasm-bindgen.workspace = true
serde_json.workspace = true

View File

@@ -61,11 +61,6 @@ const targets = [
desc: "Workflow-as-Code",
features: "wac-parser",
env: "default",
}, {
ident: "asset",
desc: "Asset parsers (TS, Python, SQL) with SQL AST",
features: "asset-parser",
env: "default",
},
# ^^^ Add new entry here ^^^
];

View File

@@ -33,6 +33,3 @@ popd
pushd "pkg-java" && npm publish ${args}
popd
pushd "pkg-asset" && npm publish ${args}
popd

View File

@@ -187,28 +187,28 @@ pub fn parse_ruby(code: &str) -> String {
wrap_sig(windmill_parser_ruby::parse_ruby_signature(code))
}
#[cfg(feature = "asset-parser")]
#[cfg(feature = "sql-parser")]
#[wasm_bindgen]
pub fn parse_assets_sql(code: &str) -> String {
match windmill_parser_sql_asset::parse_assets(code) {
match windmill_parser_sql::parse_assets(code) {
Ok(r) => serde_json::to_string(&r).unwrap(),
Err(err) => format!("err: {:?}", err),
}
}
#[cfg(feature = "asset-parser")]
#[cfg(feature = "ts-parser")]
#[wasm_bindgen]
pub fn parse_assets_ts(code: &str) -> String {
match windmill_parser_ts_asset::parse_assets(code) {
match windmill_parser_ts::parse_assets(code) {
Ok(r) => serde_json::to_string(&r).unwrap(),
Err(err) => format!("err: {:?}", err),
}
}
#[cfg(feature = "asset-parser")]
#[cfg(feature = "py-parser")]
#[wasm_bindgen]
pub fn parse_assets_py(code: &str) -> String {
match windmill_parser_py_asset::parse_assets(code) {
match windmill_parser_py::parse_assets(code) {
Ok(r) => serde_json::to_string(&r).unwrap(),
Err(err) => format!("err: {:?}", err),
}

View File

@@ -20,7 +20,6 @@ pub async fn connect_db(
server_mode: bool,
indexer_mode: bool,
worker_mode: bool,
num_workers: i32,
#[cfg(feature = "private")] mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> anyhow::Result<sqlx::Pool<sqlx::Postgres>> {
use anyhow::Context;
@@ -35,7 +34,13 @@ pub async fn connect_db(
} else if indexer_mode {
DEFAULT_MAX_CONNECTIONS_INDEXER
} else {
DEFAULT_MAX_CONNECTIONS_WORKER + (num_workers.max(1) as u32) - 1
DEFAULT_MAX_CONNECTIONS_WORKER
+ std::env::var("NUM_WORKERS")
.ok()
.map(|x| x.parse().ok())
.flatten()
.unwrap_or(1)
- 1
}
}
};
@@ -98,7 +103,7 @@ pub async fn connect(
use sqlx::Executor;
use std::time::Duration;
let mut pool_options = sqlx::postgres::PgPoolOptions::new()
.min_connections(0)
.min_connections((max_connections / 5).clamp(1, max_connections))
.max_connections(max_connections)
.max_lifetime(Duration::from_secs(30 * 60)); // 30 mins
if worker_mode {

View File

@@ -37,13 +37,12 @@ use windmill_common::ee_oss::{
use windmill_common::{
agent_workers::AgentConfig,
global_settings::{
APP_WORKSPACED_ROUTE_SETTING, AUDIT_LOG_RETENTION_DAYS_SETTING, BASE_URL_SETTING,
BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING,
CRITICAL_ALERTS_ON_TOKEN_EXPIRY_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
APP_WORKSPACED_ROUTE_SETTING, BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING,
CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING, CRITICAL_ALERTS_ON_TOKEN_EXPIRY_SETTING,
CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING,
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING,
ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING,
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING,
MAVEN_SETTINGS_XML_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING,
@@ -98,13 +97,12 @@ use windmill_worker::{
use crate::monitor::{
initial_load, load_keep_job_dir, load_metrics_debug_enabled, load_require_preexisting_user,
load_tag_per_workspace_enabled, load_tag_per_workspace_workspaces, monitor_db,
reload_app_workspaced_route_setting, reload_audit_log_retention_days_setting,
reload_base_url_setting, reload_bunfig_install_scopes_setting,
reload_critical_alert_mute_ui_setting, reload_critical_alerts_on_token_expiry_setting,
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
reload_hub_api_secret_setting, reload_hub_base_url_setting, reload_job_default_timeout_setting,
reload_job_isolation_setting, reload_jwt_secret_setting, reload_license_key,
reload_npm_config_registry_setting, reload_otel_tracing_proxy_setting,
reload_app_workspaced_route_setting, reload_base_url_setting,
reload_bunfig_install_scopes_setting, reload_critical_alert_mute_ui_setting,
reload_critical_alerts_on_token_expiry_setting, reload_critical_error_channels_setting,
reload_extra_pip_index_url_setting, reload_hub_api_secret_setting, reload_hub_base_url_setting,
reload_job_default_timeout_setting, reload_job_isolation_setting, reload_jwt_secret_setting,
reload_license_key, reload_npm_config_registry_setting, reload_otel_tracing_proxy_setting,
reload_pip_index_url_setting, reload_retention_period_setting, reload_scim_token_setting,
reload_smtp_config, reload_uv_index_strategy_setting, reload_worker_config, MonitorIteration,
};
@@ -243,14 +241,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
create_dir_all(&*HUB_CACHE_DIR)?;
create_dir_all(&*BUN_BUNDLE_CACHE_DIR)?;
// Ensure the latest git sync script is always cached, regardless of hubPaths.json contents
let mut all_paths: Vec<String> = paths.into_values().collect();
let latest_git_sync = windmill_common::workspaces::LATEST_GIT_SYNC_SCRIPT_PATH.to_string();
if !all_paths.contains(&latest_git_sync) {
all_paths.push(latest_git_sync);
}
for path in &all_paths {
for path in paths.values() {
tracing::info!("Caching hub script at {path}");
let res = get_hub_script_content_and_requirements(Some(path), None).await?;
if res
@@ -701,7 +692,6 @@ async fn windmill_main() -> anyhow::Result<()> {
let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer || mode == Mode::MCP {
0
} else if is_native_mode_from_env() {
NATIVE_MODE_RESOLVED.store(true, std::sync::atomic::Ordering::Relaxed);
println!("Native mode enabled: forcing NUM_WORKERS=8");
8
} else {
@@ -874,30 +864,6 @@ async fn windmill_main() -> anyhow::Result<()> {
}
}
// Resolve native mode early (before connect_db) so connection pool size accounts for it.
// native_mode can come from env OR from the DB worker group config.
if worker_mode && !is_native_mode_from_env() {
if let Some(db) = conn.as_sql() {
let native_from_db: bool = sqlx::query_scalar!(
"SELECT (config->>'native_mode')::boolean FROM config WHERE name = $1",
format!("worker__{}", *windmill_common::worker::WORKER_GROUP)
)
.fetch_optional(db)
.await
.ok()
.flatten()
.flatten()
.unwrap_or(false);
if native_from_db {
NATIVE_MODE_RESOLVED.store(true, std::sync::atomic::Ordering::Relaxed);
num_workers = 8;
tracing::info!(
"Native mode detected from worker config (early): forcing NUM_WORKERS=8"
);
}
}
}
let conn = if mode == Mode::Agent {
conn
} else {
@@ -910,7 +876,6 @@ async fn windmill_main() -> anyhow::Result<()> {
server_mode,
indexer_mode,
worker_mode,
num_workers,
#[cfg(feature = "private")]
killpill_rx.resubscribe(),
)
@@ -1015,6 +980,16 @@ Windmill Community Edition {GIT_VERSION}
)
.await;
// native_mode may also be set via DB worker group config (not just env).
// NATIVE_MODE_RESOLVED is updated by load_worker_config during initial_load.
if worker_mode
&& !is_native_mode_from_env()
&& NATIVE_MODE_RESOLVED.load(std::sync::atomic::Ordering::Relaxed)
{
num_workers = 8;
tracing::info!("Native mode detected from worker config: forcing NUM_WORKERS=8");
}
monitor_db(
&conn,
&base_internal_url,
@@ -1689,9 +1664,6 @@ async fn process_notify_event(
}
TIMEOUT_WAIT_RESULT_SETTING => reload_timeout_wait_result_setting(conn).await,
RETENTION_PERIOD_SECS_SETTING => reload_retention_period_setting(conn).await,
AUDIT_LOG_RETENTION_DAYS_SETTING => {
reload_audit_log_retention_days_setting(conn).await
}
MONITOR_LOGS_ON_OBJECT_STORE_SETTING => {
reload_delete_logs_periodically_setting(conn).await
}
@@ -1907,7 +1879,7 @@ pub async fn run_workers(
tracing::info!(
"Starting {num_workers} workers and SLEEP_QUEUE={}ms",
windmill_worker::sleep_queue()
*windmill_worker::SLEEP_QUEUE
);
for i in 1..(num_workers + 1) {

View File

@@ -48,17 +48,16 @@ use windmill_common::{
error,
flow_status::{FlowStatus, FlowStatusModule},
global_settings::{
AUDIT_LOG_RETENTION_DAYS_SETTING, BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING,
CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING, CRITICAL_ALERTS_ON_TOKEN_EXPIRY_SETTING,
CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
NPMRC_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OTEL_SETTING,
OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING,
CRITICAL_ALERTS_ON_TOKEN_EXPIRY_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING,
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING,
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPMRC_SETTING, NPM_CONFIG_REGISTRY_SETTING,
NUGET_CONFIG_SETTING, OTEL_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING,
POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
UV_INDEX_STRATEGY_SETTING,
@@ -78,9 +77,9 @@ use windmill_common::{
DEFAULT_TAGS_WORKSPACES, INDEXER_CONFIG, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, WINDMILL_DIR,
WORKER_CONFIG, WORKER_GROUP,
},
KillpillSender, AUDIT_LOG_RETENTION_DAYS, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE,
CRITICAL_ALERTS_ON_TOKEN_EXPIRY, CRITICAL_ALERT_MUTE_UI_ENABLED, CRITICAL_ERROR_CHANNELS, DB,
DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS, METRICS_DEBUG_ENABLED, METRICS_ENABLED,
KillpillSender, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE, CRITICAL_ALERTS_ON_TOKEN_EXPIRY,
CRITICAL_ALERT_MUTE_UI_ENABLED, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL,
HUB_BASE_URL, JOB_RETENTION_SECS, METRICS_DEBUG_ENABLED, METRICS_ENABLED,
MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED, OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED,
SERVICE_LOG_RETENTION_SECS,
};
@@ -251,8 +250,9 @@ pub async fn initial_load(
.map(|x| x.tags.clone())
.unwrap_or_default();
// we only check from env as native_mode is not stored in the token
// NATIVE_MODE_RESOLVED is already set in main.rs during startup
let native_mode = windmill_common::worker::is_native_mode_from_env();
windmill_common::worker::NATIVE_MODE_RESOLVED
.store(native_mode, std::sync::atomic::Ordering::Relaxed);
*config = WorkerConfig {
worker_tags,
env_vars: load_env_vars(
@@ -323,15 +323,9 @@ pub async fn initial_load(
if server_mode {
reload_retention_period_setting(&conn).await;
reload_audit_log_retention_days_setting(&conn).await;
reload_request_size(&conn).await;
reload_saml_metadata_setting(&conn).await;
reload_scim_token_setting(&conn).await;
// Ensure audit partitions exist before any requests arrive
if let Some(db) = conn.as_sql() {
manage_audit_partitions(&db, audit_log_retention_days().await).await;
}
}
if worker_mode {
@@ -879,19 +873,10 @@ struct TokenRow {
workspace_id: Option<String>,
}
/// When updating this filter, also update:
/// - `register_token_expiry_notification` in windmill-api-auth/src/lib.rs
/// - `isUserToken` in frontend/src/lib/components/settings/TokensTable.svelte
fn is_user_token(label: Option<&str>) -> bool {
match label {
None => true,
Some(l) => {
l != "session"
&& !l.starts_with("ephemeral")
&& !l.starts_with("Ephemeral")
&& l != "debugger-token"
&& !l.starts_with("mcp-oauth-")
}
Some(l) => l != "session" && !l.starts_with("ephemeral") && !l.starts_with("Ephemeral"),
}
}
@@ -1042,10 +1027,12 @@ pub async fn delete_expired_items(db: &DB) -> () {
Err(e) => tracing::error!("Error deleting log file: {:?}", e),
}
let audit_retention_days = audit_log_retention_days().await;
let audit_retention_secs: i64 = audit_retention_days * 60 * 60 * 24;
#[cfg(not(feature = "enterprise"))]
let audit_retention_secs = 1 * 60 * 60 * 24 * 14;
#[cfg(feature = "enterprise")]
let audit_retention_secs = 1 * 60 * 60 * 24 * 365;
// Clean up old (non-partitioned) audit table — will eventually be empty and dropped
if let Err(e) = sqlx::query_scalar!(
"DELETE FROM audit WHERE timestamp <= now() - ($1::bigint::text || ' s')::interval",
audit_retention_secs,
@@ -1053,7 +1040,7 @@ pub async fn delete_expired_items(db: &DB) -> () {
.fetch_all(db)
.await
{
tracing::error!("Error deleting audit log: {:?}", e);
tracing::error!("Error deleting audit log on CE: {:?}", e);
}
if let Err(e) = sqlx::query_scalar!(
@@ -1578,22 +1565,6 @@ pub async fn reload_retention_period_setting(conn: &Connection) {
tracing::error!("Error reloading retention period: {:?}", e)
}
}
pub async fn reload_audit_log_retention_days_setting(conn: &Connection) {
if let Err(e) = reload_setting(
conn,
AUDIT_LOG_RETENTION_DAYS_SETTING,
"AUDIT_LOG_RETENTION_DAYS",
0, // 0 means use default: 365 for EE, 14 for CE
AUDIT_LOG_RETENTION_DAYS.clone(),
|x| x,
)
.await
{
tracing::error!("Error reloading audit log retention days: {:?}", e)
}
}
pub async fn reload_delete_logs_periodically_setting(conn: &Connection) {
if let Err(e) = reload_setting(
conn,
@@ -2211,15 +2182,6 @@ pub async fn monitor_db(
}
};
// run every hour (120 iterations * 30s = 3600s)
let manage_audit_partitions_f = async {
if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(120) {
if let Some(db) = conn.as_sql() {
manage_audit_partitions(&db, audit_log_retention_days().await).await;
}
}
};
join!(
expired_items_f,
zombie_jobs_f,
@@ -2242,7 +2204,6 @@ pub async fn monitor_db(
native_triggers_sync_f,
cleanup_notify_events_f,
check_expiring_tokens_f,
manage_audit_partitions_f,
);
}
@@ -2864,7 +2825,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
&windmill_queue::MiniCompletedJob::from(job),
memory_peak,
None,
error::Error::ExecutionErr(error_message.clone()),
error::Error::ExecutionErr(error_message),
matches!(error_kind, ErrorMessage::SameWorker), // unrecoverable if the job is a same worker zombie
Some(&same_worker_tx_never_used),
"",
@@ -2875,74 +2836,10 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, node_n
&mut windmill_common::bench::BenchmarkIter::new(),
)
.await;
// If handle_job_error failed (e.g. schedule push failure rolled back the tx),
// the job is still in the queue. Force-complete it to prevent infinite zombie loops.
if let Err(e) = force_complete_zombie_job(db, &job_id, &error_message).await {
tracing::error!("Failed to force-complete zombie job {}: {e:#}", job_id);
}
}
}
}
/// Force-complete a zombie job that handle_job_error failed to complete.
/// This is a minimal fallback: it inserts a failed completed job and deletes
/// from the queue in a single transaction, without schedule pushing or
/// error handler logic that could cause the completion to fail.
async fn force_complete_zombie_job(
db: &Pool<Postgres>,
job_id: &Uuid,
error_message: &str,
) -> error::Result<()> {
let still_queued = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM v2_job_queue WHERE id = $1)",
job_id
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !still_queued {
return Ok(());
}
tracing::error!(
"Zombie job {job_id} was not completed by handle_job_error, force-completing it"
);
let error_value = serde_json::json!({
"message": error_message,
"name": "ExecutionErr",
});
let mut tx = db.begin().await?;
sqlx::query!(
"INSERT INTO v2_job_completed
(workspace_id, id, started_at, duration_ms, result, memory_peak, status, worker)
SELECT q.workspace_id, q.id, q.started_at,
COALESCE((EXTRACT('epoch' FROM now()) - EXTRACT('epoch' FROM COALESCE(q.started_at, now()))) * 1000, 0)::bigint,
$2::jsonb, r.memory_peak, 'failure'::job_status, q.worker
FROM v2_job_queue q
LEFT JOIN v2_job_runtime r ON r.id = q.id
WHERE q.id = $1
ON CONFLICT (id) DO UPDATE SET status = 'failure', result = $2::jsonb",
job_id,
error_value,
)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM v2_job_queue WHERE id = $1", job_id)
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("Force-completed zombie job {job_id}");
Ok(())
}
async fn cleanup_concurrency_counters_orphaned_keys(db: &DB) -> error::Result<()> {
let result = sqlx::query!(
"
@@ -3471,72 +3368,3 @@ RETURNING job_id
}
Ok(())
}
async fn audit_log_retention_days() -> i64 {
let v = *AUDIT_LOG_RETENTION_DAYS.read().await;
if v > 0 {
v
} else if cfg!(feature = "enterprise") {
365
} else {
14
}
}
async fn manage_audit_partitions(db: &DB, retention_days: i64) {
let today = chrono::Utc::now().date_naive();
// Create partitions for today and the next 3 days
for days_ahead in 0..=3i64 {
let date = today + chrono::Duration::days(days_ahead);
let next_date = date + chrono::Duration::days(1);
let partition_name = format!("audit_{}", date.format("%Y%m%d"));
let quoted_name = format!("\"{}\"", partition_name.replace('"', "\"\""));
let sql = format!(
"CREATE TABLE IF NOT EXISTS {quoted_name} PARTITION OF audit_partitioned \
FOR VALUES FROM ('{date}') TO ('{next_date}')"
);
if let Err(e) = sqlx::query(&sql).execute(db).await {
if !e.to_string().contains("already exists") {
tracing::error!("Error creating audit partition {partition_name}: {e:?}");
}
}
}
// Drop expired partitions
let cutoff_date = today - chrono::Duration::days(retention_days);
let partitions = sqlx::query_scalar::<_, String>(
"SELECT c.relname::text \
FROM pg_inherits i \
JOIN pg_class c ON c.oid = i.inhrelid \
WHERE i.inhparent = 'audit_partitioned'::regclass",
)
.fetch_all(db)
.await;
match partitions {
Ok(partitions) => {
for partition_name in partitions {
if let Some(date_str) = partition_name.strip_prefix("audit_") {
if let Ok(date) = chrono::NaiveDate::parse_from_str(date_str, "%Y%m%d") {
if date < cutoff_date {
let quoted_name =
format!("\"{}\"", partition_name.replace('"', "\"\""));
let sql = format!("DROP TABLE IF EXISTS {quoted_name}");
match sqlx::query(&sql).execute(db).await {
Ok(_) => tracing::info!(
"Dropped expired audit partition {partition_name}"
),
Err(e) => tracing::error!(
"Error dropping audit partition {partition_name}: {e:?}"
),
}
}
}
}
}
}
Err(e) => tracing::error!("Error listing audit partitions: {e:?}"),
}
}

View File

@@ -109,9 +109,7 @@ job_result_stream_v2: job_id(uuid), workspace_id(text), stream(text), idx(int)
job_settings: job_id(uuid), runnable_settings(bigint)
job_stats: workspace_id(char), job_id(uuid), metric_id(char), metric_name(char), metric_kind(metric_kind), scalar_int(int), scalar_float(float), timestamps(ts), timeseries_int(int[]), timeseries_float(float[])
FK: (workspace_id) -> workspace(id)
kafka_pending_commits: id(bigint), workspace_id(char), kafka_trigger_path(char), topic(char), partition(int), offset(bigint), created_at(ts)
FK: (workspace_id, kafka_trigger_path) -> kafka_trigger(workspace_id, path)
kafka_trigger: path(char), kafka_resource_path(char), topics(char), group_id(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), filters(jsonb[]), auto_commit(bool)
kafka_trigger: path(char), kafka_resource_path(char), topics(char), group_id(char), script_path(char), is_flow(bool), workspace_id(char), edited_by(char), email(char), edited_at(ts), extra_perms(jsonb), server_id(char), last_server_ping(ts), error(text), error_handler_path(char), error_handler_args(jsonb), retry(jsonb), mode(trigger_mode), filters(jsonb[])
log_file: hostname(char), log_ts(ts), ok_lines(bigint), err_lines(bigint), mode(log_mode), worker_group(char), file_path(char), json_fmt(bool)
magic_link: email(char), token(char), expiration(ts)
mcp_oauth_client: mcp_server_url(text), client_id(text), client_secret(text), client_secret_expires_at(ts), token_endpoint(text), created_at(ts)

View File

@@ -1518,92 +1518,6 @@ Write-Output "hello $msg"
Ok(())
}
#[sqlx::test(fixtures("base"))]
async fn test_powershell_param_block_with_attributes(db: Pool<Postgres>) -> anyhow::Result<()> {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await?;
let port = server.addr.port();
let content = r#"
param(
[Parameter(Mandatory=$true)]
[string]$Name,
[int]$Count = 3
)
Write-Output "$Name-$Count"
"#
.to_owned();
let job = RunJob::from(JobPayload::Code(RawCode {
hash: None,
content,
path: None,
lock: None,
language: ScriptLang::Powershell,
cache_ttl: None,
cache_ignore_s3_path: None,
dedicated_worker: None,
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
.into(),
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
}))
.arg("Name", json!("test"))
.arg("Count", json!(7))
.run_until_complete(&db, false, port)
.await;
assert_eq!(job.json_result(), Some(json!("test-7")));
Ok(())
}
#[sqlx::test(fixtures("base"))]
async fn test_powershell_error_caught(db: Pool<Postgres>) -> anyhow::Result<()> {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await?;
let port = server.addr.port();
// Script with param block that throws an error — verifies the catch block works
let content = r#"
param($x)
throw "intentional error"
"#
.to_owned();
let job = RunJob::from(JobPayload::Code(RawCode {
hash: None,
content,
path: None,
lock: None,
language: ScriptLang::Powershell,
cache_ttl: None,
cache_ignore_s3_path: None,
dedicated_worker: None,
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
.into(),
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
}))
.arg("x", json!(1))
.run_until_complete(&db, false, port)
.await;
assert!(!job.success, "job should fail on thrown error");
let result_str = serde_json::to_string(&job.result).unwrap_or_default();
assert!(
result_str.contains("An error occurred:"),
"catch block should output 'An error occurred:', got: {result_str}"
);
assert!(
result_str.contains("intentional error"),
"catch block should output the error message, got: {result_str}"
);
// Verify the catch block doesn't leak "Write-Output" as literal text
// (regression from the old broken line continuation in strict_termination_end)
let after_marker = result_str.split("An error occurred:").nth(1).unwrap_or("");
assert!(
!after_marker.starts_with("\\nWrite-Output"),
"catch block should not output literal 'Write-Output' text, got: {result_str}"
);
Ok(())
}
#[cfg(feature = "php")]
#[sqlx::test(fixtures("base"))]
async fn test_php_job(db: Pool<Postgres>) -> anyhow::Result<()> {

View File

@@ -581,9 +581,6 @@ pub async fn create_token_internal(
}
/// Insert a pending expiry notification row for user tokens that have an expiration.
/// When updating this filter, also update:
/// - `is_user_token` in src/monitor.rs
/// - `isUserToken` in frontend/src/lib/components/settings/TokensTable.svelte
pub async fn register_token_expiry_notification(
tx: &mut sqlx::PgConnection,
token: &str,
@@ -592,12 +589,7 @@ pub async fn register_token_expiry_notification(
) {
let Some(expiration) = expiration else { return };
if label == Some("session")
|| label.is_some_and(|l| {
l.starts_with("ephemeral")
|| l.starts_with("Ephemeral")
|| l == "debugger-token"
|| l.starts_with("mcp-oauth-")
})
|| label.is_some_and(|l| l.starts_with("ephemeral") || l.starts_with("Ephemeral"))
{
return;
}

View File

@@ -129,12 +129,11 @@ async fn update_config(
#[cfg(not(feature = "enterprise"))]
let config = if name.starts_with("worker__") {
// In CE, only allow setting worker_tags, cache_clear, init_bash, and native_mode
// In CE, only allow setting worker_tags, cache_clear, and init_bash
serde_json::json!({
"worker_tags": config.get("worker_tags"),
"cache_clear": config.get("cache_clear"),
"init_bash": config.get("init_bash"),
"native_mode": config.get("native_mode")
"init_bash": config.get("init_bash")
})
} else {
config

View File

@@ -10,8 +10,8 @@ path = "src/lib.rs"
[features]
default = []
private = ["windmill-test-utils/private", "dep:aws-config", "dep:aws-credential-types", "dep:aws-sdk-sqs", "windmill-git-sync/private"]
enterprise = ["windmill-test-utils/enterprise", "dep:base64", "windmill-git-sync/enterprise"]
private = ["windmill-test-utils/private", "dep:aws-config", "dep:aws-credential-types", "dep:aws-sdk-sqs"]
enterprise = ["windmill-test-utils/enterprise", "dep:base64"]
deno_core = ["windmill-test-utils/deno_core"]
mcp = []
run_inline = ["dep:windmill-worker", "windmill-test-utils/run_inline", "windmill-test-utils/duckdb"]
@@ -22,7 +22,6 @@ windmill-api-client.workspace = true
windmill-common = { workspace = true, default-features = false }
windmill-native-triggers = { workspace = true, features = ["native_trigger"] }
windmill-api-auth.workspace = true
windmill-git-sync.workspace = true
windmill-worker = { workspace = true, optional = true }
sqlx.workspace = true
serde_json.workspace = true

View File

@@ -251,8 +251,7 @@ async fn test_websocket_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
"test-workspace",
"test-user",
"test@windmill.dev",
&[json!({"type": "RawMessage", "content": "hello from e2e test"})]
as &[serde_json::Value],
&[json!({"type": "RawMessage", "content": "hello from e2e test"})] as &[serde_json::Value],
)
.execute(&db)
.await?;
@@ -303,11 +302,9 @@ async fn test_postgres_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
sqlx::query("CREATE TABLE test_trigger_table (id serial PRIMARY KEY, data text)")
.execute(&db)
.await?;
sqlx::query(&format!(
"CREATE PUBLICATION {pub_name} FOR TABLE test_trigger_table"
))
.execute(&db)
.await?;
sqlx::query(&format!("CREATE PUBLICATION {pub_name} FOR TABLE test_trigger_table"))
.execute(&db)
.await?;
sqlx::query(&format!(
"SELECT pg_create_logical_replication_slot('{slot_name}', 'pgoutput')"
))
@@ -316,9 +313,10 @@ async fn test_postgres_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
// Extract the test DB name from the pool so the resource points here,
// not at the main windmill database.
let test_db_name: String = sqlx::query_scalar("SELECT current_database()")
.fetch_one(&db)
.await?;
let test_db_name: String =
sqlx::query_scalar("SELECT current_database()")
.fetch_one(&db)
.await?;
insert_resource(
&db,

View File

@@ -214,9 +214,12 @@ async fn test_capture_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
.execute(&db)
.await?;
let count = sqlx::query_scalar!("SELECT COUNT(*) FROM capture WHERE id = $1", id,)
.fetch_one(&db)
.await?;
let count = sqlx::query_scalar!(
"SELECT COUNT(*) FROM capture WHERE id = $1",
id,
)
.fetch_one(&db)
.await?;
assert_eq!(count, Some(0));
@@ -382,10 +385,7 @@ async fn test_capture_api_list_captures(db: Pool<Postgres>) -> anyhow::Result<()
.send()
.await?;
assert!(
response.status().is_success(),
"list captures should succeed"
);
assert!(response.status().is_success(), "list captures should succeed");
let captures: Vec<CaptureResponse> = response.json().await?;
assert_eq!(captures.len(), 3);
@@ -480,9 +480,12 @@ async fn test_capture_api_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
assert!(response.status().is_success(), "delete should succeed");
let count = sqlx::query_scalar!("SELECT COUNT(*) FROM capture WHERE id = $1", id,)
.fetch_one(&db)
.await?;
let count = sqlx::query_scalar!(
"SELECT COUNT(*) FROM capture WHERE id = $1",
id,
)
.fetch_one(&db)
.await?;
assert_eq!(count, Some(0));
@@ -930,8 +933,7 @@ async fn test_kafka_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
let trigger = sqlx::query!(
r#"
SELECT kafka_resource_path, topics, group_id, mode AS "mode: String",
auto_offset_reset, auto_commit, reset_offset
SELECT kafka_resource_path, topics, group_id, mode AS "mode: String"
FROM kafka_trigger
WHERE workspace_id = $1 AND path = $2
"#,
@@ -945,50 +947,6 @@ async fn test_kafka_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
assert_eq!(trigger.topics, vec!["topic-a", "topic-b"]);
assert_eq!(trigger.group_id, "my-consumer-group");
assert_eq!(trigger.mode, "enabled");
assert_eq!(trigger.auto_offset_reset, "latest");
assert_eq!(trigger.auto_commit, true);
assert_eq!(trigger.reset_offset, false);
Ok(())
}
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
async fn test_kafka_trigger_insert_auto_commit_disabled(db: Pool<Postgres>) -> anyhow::Result<()> {
sqlx::query!(
r#"
INSERT INTO kafka_trigger (
path, kafka_resource_path, topics, group_id, script_path,
is_flow, workspace_id, edited_by, email, auto_commit
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
"#,
"f/test/kafka_trigger_no_commit",
"u/admin/kafka_resource",
&["topic-c"] as &[&str],
"my-consumer-group-2",
"f/test/kafka_handler",
false,
"test-workspace",
"test-user",
"test@windmill.dev",
false,
)
.execute(&db)
.await?;
let trigger = sqlx::query!(
r#"
SELECT auto_commit
FROM kafka_trigger
WHERE workspace_id = $1 AND path = $2
"#,
"test-workspace",
"f/test/kafka_trigger_no_commit",
)
.fetch_one(&db)
.await?;
assert_eq!(trigger.auto_commit, false);
Ok(())
}

View File

@@ -570,7 +570,6 @@ pub async fn send_stats(Extension(db): Extension<DB>, authed: ApiAuthed) -> Resu
&HTTP_CLIENT,
&db,
windmill_common::stats_oss::SendStatsReason::Manual,
false,
)
.await?;
@@ -583,7 +582,6 @@ pub async fn get_stats(Extension(db): Extension<DB>, authed: ApiAuthed) -> Resul
let stats = windmill_common::stats_oss::get_stats_payload(
&db,
&windmill_common::stats_oss::SendStatsReason::Manual,
false,
)
.await?;
let encrypted = windmill_common::stats_oss::encrypt_stats(&stats)?;

View File

@@ -393,7 +393,7 @@ async fn list_users_as_super_admin(
let rows = if active_only.is_some_and(|x| x) {
sqlx::query_as!(
GlobalUserInfo,
"WITH active_users AS (SELECT distinct username as email FROM (SELECT username, timestamp, operation FROM audit_partitioned UNION ALL SELECT username, timestamp, operation FROM audit) AS a WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),
"WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),
authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)
SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username, first_time_user
FROM password

View File

@@ -63,7 +63,6 @@ windmill-object-store.workspace = true
windmill-audit.workspace = true
windmill-parser.workspace = true
windmill-parser-sql.workspace = true
windmill-parser-sql-asset.workspace = true
windmill-parser-ts.workspace = true
windmill-parser-py = { workspace = true, optional = true }
windmill-parser-py-imports = { workspace = true, optional = true }

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.657.2
version: 1.653.0
title: Windmill API
contact:
@@ -1939,58 +1939,6 @@ paths:
"200":
description: Successfully imported the installation
/w/{workspace}/github_app/ghes_installation_callback:
post:
summary: GHES installation callback
description: Register a self-managed GitHub App installation from GitHub Enterprise Server
operationId: ghesInstallationCallback
tags:
- Git Sync
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- installation_id
properties:
installation_id:
type: integer
format: int64
description: The GitHub App installation ID from GHES
responses:
"200":
description: GHES installation registered successfully
/github_app/ghes_config:
get:
summary: Get GHES app config
description: Returns the GitHub Enterprise Server app configuration (without private key) for constructing the installation URL
operationId: getGhesConfig
tags:
- Git Sync
responses:
"200":
description: GHES app configuration
content:
application/json:
schema:
type: object
properties:
base_url:
type: string
app_slug:
type: string
client_id:
type: string
required:
- base_url
- app_slug
- client_id
/users/accept_invite:
post:
summary: accept invite to workspace
@@ -12069,39 +12017,6 @@ paths:
"200":
description: kafka trigger offsets reset successfully
/w/{workspace}/kafka_triggers/commit_offsets/{path}:
post:
summary: commit kafka offsets for a specific trigger
operationId: commitKafkaOffsets
tags:
- kafka_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
requestBody:
description: offsets to commit
required: true
content:
application/json:
schema:
type: object
properties:
topic:
type: string
partition:
type: integer
format: int32
offset:
type: integer
format: int64
required:
- topic
- partition
- offset
responses:
"200":
description: kafka offsets committed successfully
/w/{workspace}/nats_triggers/create:
post:
summary: create nats trigger
@@ -22183,10 +22098,6 @@ components:
- earliest
default: latest
description: "Initial offset behavior when consumer group has no committed offset. 'latest' starts from new messages only, 'earliest' starts from the beginning."
auto_commit:
type: boolean
default: true
description: "When true (default), offsets are committed automatically after receiving each message. When false, you must manually commit offsets using the commit_offsets endpoint."
server_id:
type: string
description: ID of the server currently handling this trigger (internal)
@@ -22254,10 +22165,6 @@ components:
- earliest
default: latest
description: "Initial offset behavior when consumer group has no committed offset."
auto_commit:
type: boolean
default: true
description: "When true (default), offsets are committed automatically after receiving each message. When false, you must manually commit offsets using the commit_offsets endpoint."
mode:
$ref: "#/components/schemas/TriggerMode"
error_handler_path:
@@ -22317,10 +22224,6 @@ components:
- earliest
default: latest
description: "Initial offset behavior when consumer group has no committed offset."
auto_commit:
type: boolean
default: true
description: "When true (default), offsets are committed automatically after receiving each message. When false, you must manually commit offsets using the commit_offsets endpoint."
path:
type: string
description: The unique path identifier for this trigger
@@ -23629,6 +23532,7 @@ components:
items:
$ref: "#/components/schemas/GitSyncObjectType"
required:
- script_path
- git_repo_resource_path
MetricMetadata:

View File

@@ -1012,7 +1012,6 @@ async fn http_payload(
.to_v2_preprocessor_args(
&http_trigger_config.route_path,
&route_path,
"",
&params,
headers,
query,

View File

@@ -4833,7 +4833,7 @@ fn register_potential_assets_on_inline_execution(
preview: &PreviewInline,
) {
let assets = if preview.language == ScriptLang::DuckDb {
Some(windmill_parser_sql_asset::parse_assets(&preview.content).map(|a| a.assets))
Some(windmill_parser_sql::parse_assets(&preview.content).map(|a| a.assets))
} else if preview.language == ScriptLang::Postgresql {
let datatable = preview
.args
@@ -4851,7 +4851,7 @@ fn register_potential_assets_on_inline_execution(
(None, None)
};
let content = content.as_deref().unwrap_or(&preview.content);
windmill_parser_sql_asset::parse_wmill_sdk_sql_assets(
windmill_parser_sql::parse_wmill_sdk_sql_assets(
AssetKind::DataTable,
datatable,
schema.as_deref(),

View File

@@ -468,7 +468,6 @@ async fn route_job(
.to_args_from_format(
&trigger.route_path,
&called_path,
&trigger.path,
&params,
runnable_format,
trigger.wrap_body,

View File

@@ -68,7 +68,6 @@ struct HttpTriggerPreprocessorEvent<'a> {
kind: String,
route: &'a str,
path: &'a str,
trigger_path: &'a str,
body: Box<RawValue>,
raw_string: Option<String>,
params: &'a HashMap<String, String>,
@@ -118,7 +117,6 @@ impl HttpTriggerArgs {
self,
route_path: &str,
called_path: &str,
trigger_path: &str,
params: &HashMap<String, String>,
format: RunnableFormat,
wrap_body: bool,
@@ -128,14 +126,7 @@ impl HttpTriggerArgs {
match format {
RunnableFormat { has_preprocessor: true, version: RunnableFormatVersion::V2 } => {
// we don't care about wrap_body in v2
self.to_v2_preprocessor_args(
route_path,
called_path,
trigger_path,
params,
headers,
query,
)
self.to_v2_preprocessor_args(route_path, called_path, params, headers, query)
}
RunnableFormat { has_preprocessor: true, version: RunnableFormatVersion::V1 } => self
.to_v1_preprocessor_args(
@@ -186,7 +177,6 @@ impl HttpTriggerArgs {
self,
route_path: &str,
called_path: &str,
trigger_path: &str,
params: &HashMap<String, String>,
headers: HashMap<String, Box<RawValue>>,
query: HashMap<String, Box<RawValue>>,
@@ -203,7 +193,6 @@ impl HttpTriggerArgs {
method: (&self.0.metadata.method).try_into()?,
route: route_path,
path: called_path,
trigger_path,
params,
}),
);

View File

@@ -283,8 +283,6 @@ 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>,
@@ -325,8 +323,6 @@ 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>,
@@ -351,7 +347,6 @@ 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>,
@@ -960,7 +955,6 @@ pub(crate) async fn tarball_workspace(
mute_critical_alerts,
color,
operator_settings,
datatable,
slack_team_id,
slack_name,
slack_command_script
@@ -989,7 +983,6 @@ 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(),
@@ -1052,7 +1045,6 @@ 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,

View File

@@ -339,7 +339,7 @@ async fn fetch_authed_from_permissioned_as_inner(
if let Some(r) = r {
(r.is_admin, r.operator)
} else {
return Err(Error::NotFound(format!(
return Err(Error::internal_err(format!(
"user {name} not found in workspace {w_id}"
)));
}

View File

@@ -21,14 +21,15 @@ pub fn prepend_token_to_github_url(
) -> crate::error::Result<String> {
let url = Url::parse(github_url)?;
let host = url.host_str().ok_or_else(|| {
crate::error::Error::BadRequest("Invalid GitHub URL: no host".to_string())
})?;
if url.host_str() != Some("github.com") {
return Err(crate::error::Error::BadRequest(
"Invalid: not a github URL".to_string(),
));
}
Ok(format!(
"https://x-access-token:{}@{}{}",
"https://x-access-token:{}@github.com{}",
installation_token,
host,
url.path()
))
}

View File

@@ -4,7 +4,6 @@ pub const DEFAULT_TAGS_WORKSPACES_SETTING: &str = "default_tags_workspaces";
pub const BASE_URL_SETTING: &str = "base_url";
pub const OAUTH_SETTING: &str = "oauths";
pub const RETENTION_PERIOD_SECS_SETTING: &str = "retention_period_secs";
pub const AUDIT_LOG_RETENTION_DAYS_SETTING: &str = "audit_log_retention_days";
pub const MONITOR_LOGS_ON_OBJECT_STORE_SETTING: &str = "monitor_logs_on_s3";
pub const JOB_DEFAULT_TIMEOUT_SECS_SETTING: &str = "job_default_timeout";
pub const REQUEST_SIZE_LIMIT_SETTING: &str = "request_size_limit_mb";
@@ -58,7 +57,6 @@ pub const OTEL_TRACING_PROXY_SETTING: &str = "otel_tracing_proxy";
pub const APP_WORKSPACED_ROUTE_SETTING: &str = "app_workspaced_route";
pub const SECRET_BACKEND_SETTING: &str = "secret_backend";
pub const MIN_KEEP_ALIVE_VERSION_SETTING: &str = "min_keep_alive_version";
pub const GITHUB_ENTERPRISE_APP_SETTING: &str = "github_enterprise_app";
pub const ENV_SETTINGS: &[&str] = &[
"DISABLE_NSJAIL",

View File

@@ -21,9 +21,9 @@ pub struct TantivyIndexerSettings {
impl Default for TantivyIndexerSettings {
fn default() -> Self {
TantivyIndexerSettings {
writer_memory_budget: 150_000_000,
commit_job_max_batch_size: 10_000,
commit_log_max_batch_size: 5_000,
writer_memory_budget: 300_000_000,
commit_job_max_batch_size: 50_000,
commit_log_max_batch_size: 10_000,
refresh_index_period: 300,
refresh_log_index_period: 300,
max_indexed_job_log_size: 1_000_000,

View File

@@ -205,7 +205,6 @@ lazy_static::lazy_static! {
pub static ref CRITICAL_ALERTS_ON_DB_OVERSIZE: Arc<RwLock<Option<f32>>> = Arc::new(RwLock::new(None));
pub static ref JOB_RETENTION_SECS: Arc<RwLock<i64>> = Arc::new(RwLock::new(0));
pub static ref AUDIT_LOG_RETENTION_DAYS: Arc<RwLock<i64>> = Arc::new(RwLock::new(0));
pub static ref MONITOR_LOGS_ON_OBJECT_STORE: Arc<RwLock<bool>> = Arc::new(RwLock::new(false));

View File

@@ -32,7 +32,6 @@ pub async fn send_stats(
_http_client: &reqwest::Client,
_db: &DB,
_reason: SendStatsReason,
_minimal: bool,
) -> Result<()> {
// stats details are closed source
Ok(())
@@ -57,11 +56,7 @@ pub async fn get_user_usage<'c, E: sqlx::Executor<'c, Database = Postgres>>(
pub struct Stats {}
#[cfg(not(feature = "private"))]
pub async fn get_stats_payload(
_db: &DB,
_reason: &SendStatsReason,
_minimal: bool,
) -> Result<Stats> {
pub async fn get_stats_payload(_db: &DB, _reason: &SendStatsReason) -> Result<Stats> {
// stats details are closed source
Ok(Stats {})
}

View File

@@ -149,16 +149,11 @@ pub enum ObjectType {
WorkspaceDependencies,
}
pub const LATEST_GIT_SYNC_SCRIPT_PATH: &str = "hub/28160/sync-script-to-git-repo-windmill";
#[derive(Serialize, Deserialize, Debug)]
pub struct GitRepositorySettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude_types_override: Option<Vec<ObjectType>>,
/// None means auto-managed: always use LATEST_GIT_SYNC_SCRIPT_PATH.
/// Some(path) means pinned to a specific script.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub script_path: Option<String>,
pub script_path: String,
pub git_repo_resource_path: String,
pub use_individual_branch: Option<bool>,
pub group_by_folder: Option<bool>,
@@ -169,26 +164,23 @@ pub struct GitRepositorySettings {
}
impl GitRepositorySettings {
pub fn effective_script_path(&self) -> &str {
self.script_path
.as_deref()
.unwrap_or(LATEST_GIT_SYNC_SCRIPT_PATH)
}
pub fn is_script_meets_min_version(&self, min_version: u32) -> error::Result<bool> {
let path = self.effective_script_path();
// example: "hub/28102/sync-script-to-git-repo-windmill"
let current = path
let current = self
.script_path
.split("/") // -> ["hub" "28102" "sync-script-to-git-repo-windmill"]
.skip(1) // omit "hub"
.next() // get numeric id
.ok_or(Error::InternalErr(format!(
"cannot get script version id from: {}",
path
&self.script_path
)))?
.parse()
.unwrap_or_else(|e| {
tracing::warn!("cannot get script version id from: {}. e: {e}", path);
tracing::warn!(
"cannot get script version id from: {}. e: {e}",
&self.script_path
);
u32::MAX
});

View File

@@ -3030,16 +3030,8 @@ 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(_) => {
// 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}")));
}
}
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}")));
}
}
}
@@ -5521,11 +5513,10 @@ async fn push_inner<'c, 'd>(
&mut *tx,
)
.await
.map_err(|e| match e {
Error::NotFound(_) => e,
_ => Error::internal_err(format!(
.map_err(|e| {
Error::internal_err(format!(
"Could not get permissions directly for job {job_id}: {e:#}"
)),
))
})?
}
};

View File

@@ -891,12 +891,12 @@ async fn delete_resource(
.fetch_optional(&mut *tx)
.await?;
not_found_if_none(deleted_path, "Resource", &path)?;
let deleted_linked_variable = sqlx::query_scalar!(
"DELETE FROM variable WHERE path = $1 AND workspace_id = $2 RETURNING path",
sqlx::query!(
"DELETE FROM variable WHERE path = $1 AND workspace_id = $2",
path,
w_id
)
.fetch_optional(&mut *tx)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
@@ -924,34 +924,9 @@ async fn delete_resource(
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteResource { workspace: w_id.clone(), path: path.to_owned() },
WebhookMessage::DeleteResource { workspace: w_id, path: path.to_owned() },
);
if deleted_linked_variable.is_some() {
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Variable {
path: path.to_string(),
parent_path: Some(path.to_string()),
},
Some(format!(
"Variable '{}' deleted (linked resource deleted)",
path
)),
true,
None,
)
.await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteVariable { workspace: w_id, path: path.to_owned() },
);
}
Ok(format!("resource {} deleted", path))
}

View File

@@ -536,12 +536,12 @@ async fn delete_variable(
)
.execute(&mut *tx)
.await?;
let deleted_linked_resource = sqlx::query_scalar!(
"DELETE FROM resource WHERE path = $1 AND workspace_id = $2 RETURNING path",
sqlx::query!(
"DELETE FROM resource WHERE path = $1 AND workspace_id = $2",
path,
w_id
)
.fetch_optional(&mut *tx)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
@@ -575,34 +575,9 @@ async fn delete_variable(
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteVariable { workspace: w_id.clone(), path: path.to_owned() },
WebhookMessage::DeleteVariable { workspace: w_id, path: path.to_owned() },
);
if deleted_linked_resource.is_some() {
handle_deployment_metadata(
&authed.email,
&authed.username,
&db,
&w_id,
DeployedObject::Resource {
path: path.to_string(),
parent_path: Some(path.to_string()),
},
Some(format!(
"Resource '{}' deleted (linked variable deleted)",
path
)),
true,
None,
)
.await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteResource { workspace: w_id, path: path.to_owned() },
);
}
Ok(format!("variable {} deleted", path))
}

Some files were not shown because too many files have changed in this diff Show More