Compare commits
1 Commits
fix/inline
...
fix-variab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cc3928726 |
@@ -13,10 +13,8 @@ fi
|
|||||||
# Check if the file is in the backend directory and is a Rust file
|
# Check if the file is in the backend directory and is a Rust file
|
||||||
if [[ "$FILE_PATH" == *"/backend/"* ]] && [[ "$FILE_PATH" =~ \.rs$ ]]; then
|
if [[ "$FILE_PATH" == *"/backend/"* ]] && [[ "$FILE_PATH" =~ \.rs$ ]]; then
|
||||||
cd "$CLAUDE_PROJECT_DIR/backend" || exit 0
|
cd "$CLAUDE_PROJECT_DIR/backend" || exit 0
|
||||||
# Run rustfmt, surface errors as context but don't block Claude
|
# Run rustfmt with config from rustfmt.toml (edition=2021)
|
||||||
if rustfmt --config-path rustfmt.toml "$FILE_PATH" 2>&1; then
|
rustfmt --config-path rustfmt.toml "$FILE_PATH" 2>/dev/null || true
|
||||||
echo "Formatted $(basename "$FILE_PATH")"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -15,10 +15,8 @@ if [[ "$FILE_PATH" == *"/frontend/"* ]]; then
|
|||||||
# Check if it's a formattable file type
|
# Check if it's a formattable file type
|
||||||
if [[ "$FILE_PATH" =~ \.(ts|js|svelte|json|css|html|md)$ ]]; then
|
if [[ "$FILE_PATH" =~ \.(ts|js|svelte|json|css|html|md)$ ]]; then
|
||||||
cd "$CLAUDE_PROJECT_DIR/frontend" || exit 0
|
cd "$CLAUDE_PROJECT_DIR/frontend" || exit 0
|
||||||
# Run prettier, surface errors as context but don't block Claude
|
# Run prettier silently, don't fail the hook if prettier fails
|
||||||
if ./node_modules/.bin/prettier --plugin prettier-plugin-svelte --write "$FILE_PATH" 2>&1; then
|
npx prettier --write "$FILE_PATH" 2>/dev/null || true
|
||||||
echo "Formatted $(basename "$FILE_PATH")"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,6 @@
|
|||||||
"Bash(git show:*)",
|
"Bash(git show:*)",
|
||||||
"Bash(git blame:*)",
|
"Bash(git blame:*)",
|
||||||
"Bash(cargo check:*)",
|
"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",
|
"mcp__ide__getDiagnostics",
|
||||||
"Bash(npm run generate-backend-client:*)",
|
"Bash(npm run generate-backend-client:*)",
|
||||||
"Bash(npm run check:*)",
|
"Bash(npm run check:*)",
|
||||||
|
|||||||
@@ -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="[...]"
|
|
||||||
```
|
|
||||||
@@ -33,7 +33,6 @@ Follow conventional commit format for the PR title:
|
|||||||
- Keep under 70 characters
|
- Keep under 70 characters
|
||||||
- Use lowercase, imperative mood
|
- Use lowercase, imperative mood
|
||||||
- No period at the end
|
- No period at the end
|
||||||
- If `*_ee.rs` files were modified, prefix with `[ee]`: `[ee] <type>: <description>`
|
|
||||||
|
|
||||||
## PR Body Format
|
## PR Body Format
|
||||||
|
|
||||||
@@ -86,25 +85,3 @@ Generated with [Claude Code](https://claude.com/claude-code)
|
|||||||
)"
|
)"
|
||||||
```
|
```
|
||||||
7. Return the PR URL to the user
|
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 1–5 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
|
|
||||||
|
|||||||
12
.github/workflows/backend-check.yml
vendored
12
.github/workflows/backend-check.yml
vendored
@@ -119,18 +119,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache-workspaces: backend
|
cache-workspaces: backend
|
||||||
toolchain: 1.93.0
|
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
|
- name: cargo check
|
||||||
timeout-minutes: 16
|
timeout-minutes: 16
|
||||||
working-directory: ./backend
|
working-directory: ./backend
|
||||||
|
|||||||
13
.github/workflows/backend-test.yml
vendored
13
.github/workflows/backend-test.yml
vendored
@@ -1,7 +1,6 @@
|
|||||||
name: Backend only integration tests
|
name: Backend only integration tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
@@ -89,18 +88,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache-workspaces: backend
|
cache-workspaces: backend
|
||||||
toolchain: 1.93.0
|
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
|
- name: Read EE repo commit hash
|
||||||
run: |
|
run: |
|
||||||
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||||
|
|||||||
37
.github/workflows/check-system-prompts.yml
vendored
37
.github/workflows/check-system-prompts.yml
vendored
@@ -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
|
|
||||||
209
.github/workflows/git-sync-test.yml
vendored
209
.github/workflows/git-sync-test.yml
vendored
@@ -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
|
|
||||||
4
.github/workflows/npm_on_release.yml
vendored
4
.github/workflows/npm_on_release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: "20.x"
|
node-version: "20.x"
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
- run: cd typescript-client && ./publish.sh --access public && cd ..
|
- run: cd typescript-client && ./publish.sh && cd ..
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
publish_cli:
|
publish_cli:
|
||||||
@@ -28,6 +28,6 @@ jobs:
|
|||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
- run: cd cli && ./build.sh && cd npm && npm publish --access public
|
- run: cd cli && ./build.sh && cd npm && npm publish
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,4 +27,3 @@ typescript-client/node_modules
|
|||||||
frontend/.svelte-kit
|
frontend/.svelte-kit
|
||||||
backend/chrome_profiler.json
|
backend/chrome_profiler.json
|
||||||
.fast-check/
|
.fast-check/
|
||||||
__pycache__/
|
|
||||||
|
|||||||
105
.webmux.yaml
105
.webmux.yaml
@@ -1,105 +0,0 @@
|
|||||||
# Project display name in the dashboard
|
|
||||||
name: Windmill
|
|
||||||
|
|
||||||
workspace:
|
|
||||||
mainBranch: main
|
|
||||||
worktreeRoot: ../windmill__worktrees
|
|
||||||
defaultAgent: claude
|
|
||||||
|
|
||||||
startupEnvs:
|
|
||||||
CARGO_FEATURES: "quickjs"
|
|
||||||
WM_CLONE_DB: false
|
|
||||||
USE_RUST_PLUGIN: false
|
|
||||||
|
|
||||||
lifecycleHooks:
|
|
||||||
postCreate: bash ./scripts/post-create.sh
|
|
||||||
preRemove: bash ./scripts/pre-remove.sh
|
|
||||||
|
|
||||||
auto_name:
|
|
||||||
provider: claude
|
|
||||||
model: haiku
|
|
||||||
|
|
||||||
# Each service defines a port env var that webmux injects into pane and agent
|
|
||||||
# process environments when creating a worktree. Ports are auto-assigned:
|
|
||||||
# base + (slot x step).
|
|
||||||
services:
|
|
||||||
- name: backend
|
|
||||||
portEnv: BACKEND_PORT
|
|
||||||
portStart: 8000
|
|
||||||
portStep: 10
|
|
||||||
- name: frontend
|
|
||||||
portEnv: FRONTEND_PORT
|
|
||||||
portStart: 3000
|
|
||||||
portStep: 10
|
|
||||||
|
|
||||||
profiles:
|
|
||||||
full:
|
|
||||||
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: backend (cargo watch -x run)
|
|
||||||
- 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
|
|
||||||
focus: true
|
|
||||||
- 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}"
|
|
||||||
- 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
|
|
||||||
|
|
||||||
integrations:
|
|
||||||
github:
|
|
||||||
linkedRepos:
|
|
||||||
- repo: windmill-labs/windmill-ee-private
|
|
||||||
alias: ee-private
|
|
||||||
dir: ../windmill-ee-private__worktrees
|
|
||||||
linear:
|
|
||||||
enabled: true
|
|
||||||
113
.wmdev.yaml
Normal file
113
.wmdev.yaml
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
name: Windmill
|
||||||
|
|
||||||
|
startupEnvs:
|
||||||
|
CARGO_FEATURES: "quickjs"
|
||||||
|
WM_CLONE_DB: false
|
||||||
|
USE_RUST_PLUGIN: false
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: BE
|
||||||
|
portEnv: BACKEND_PORT
|
||||||
|
- name: FE
|
||||||
|
portEnv: FRONTEND_PORT
|
||||||
|
|
||||||
|
profiles:
|
||||||
|
default:
|
||||||
|
name: default
|
||||||
|
|
||||||
|
sandbox:
|
||||||
|
name: sandbox
|
||||||
|
image: windmill-sandbox
|
||||||
|
envPassthrough:
|
||||||
|
- AWS_ACCESS_KEY_ID
|
||||||
|
- AWS_SECRET_ACCESS_KEY
|
||||||
|
- R2_ENDPOINT
|
||||||
|
- R2_BUCKET
|
||||||
|
- R2_PUBLIC_URL
|
||||||
|
extraMounts:
|
||||||
|
- hostPath: ~/.ssh
|
||||||
|
guestPath: /root/.ssh
|
||||||
|
writable: true
|
||||||
|
- hostPath: ~/.codex
|
||||||
|
guestPath: /root/.codex
|
||||||
|
writable: true
|
||||||
|
- hostPath: ~/windmill-ee-private
|
||||||
|
writable: true
|
||||||
|
- hostPath: ~/windmill-ee-private__worktrees
|
||||||
|
writable: true
|
||||||
|
systemPrompt: >
|
||||||
|
You are running inside a sandboxed container with full permissions.
|
||||||
|
This worktree is configured with the following ports:
|
||||||
|
|
||||||
|
- Backend: port ${BACKEND_PORT}.
|
||||||
|
Start with: cd backend && PORT=${BACKEND_PORT}
|
||||||
|
DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill
|
||||||
|
cargo watch -x run
|
||||||
|
|
||||||
|
- Frontend: port ${FRONTEND_PORT}.
|
||||||
|
Start with: cd frontend && REMOTE=http://localhost:${BACKEND_PORT}
|
||||||
|
npm run dev -- --port ${FRONTEND_PORT} --host 0.0.0.0
|
||||||
|
|
||||||
|
--- Screenshots ---
|
||||||
|
You can take screenshots of the frontend UI and upload them to R2
|
||||||
|
for use in PR descriptions.
|
||||||
|
1) Take a screenshot:
|
||||||
|
bunx playwright screenshot --browser chromium
|
||||||
|
http://localhost:${FRONTEND_PORT}/path/to/page /tmp/screenshot.png
|
||||||
|
2) Upload to R2:
|
||||||
|
aws s3 cp /tmp/screenshot.png
|
||||||
|
"s3://$(printenv R2_BUCKET)/$(git rev-parse --abbrev-ref HEAD)/screenshot.png"
|
||||||
|
--endpoint-url "$(printenv R2_ENDPOINT)"
|
||||||
|
3) The public URL will be:
|
||||||
|
$(printenv R2_PUBLIC_URL)/<branch>/screenshot.png
|
||||||
|
4) Include in PR descriptions using markdown image syntax.
|
||||||
|
|
||||||
|
--- Terminal Recordings (asciinema) ---
|
||||||
|
You can record terminal sessions and upload them for sharing.
|
||||||
|
asciinema is available on PATH.
|
||||||
|
|
||||||
|
1) Write a shell script with the commands to demo. Add sleep
|
||||||
|
delays for readable pacing:
|
||||||
|
- 0.5s after printing a "$ command" line (lets viewer read it)
|
||||||
|
- 1.5-2s after command output (lets viewer absorb the result)
|
||||||
|
- Set GIT_PAGER=cat and PAGER=cat to prevent pager hangs
|
||||||
|
|
||||||
|
2) Record headlessly:
|
||||||
|
asciinema rec --headless --overwrite \
|
||||||
|
-c "bash /tmp/demo.sh" \
|
||||||
|
--window-size 120x50 \
|
||||||
|
--title "Description of demo" \
|
||||||
|
/tmp/demo.cast
|
||||||
|
|
||||||
|
3) Upload to asciinema.org:
|
||||||
|
XDG_DATA_HOME=/tmp/.local/share \
|
||||||
|
asciinema upload --server-url https://asciinema.org /tmp/demo.cast
|
||||||
|
|
||||||
|
--- Mermaid Diagrams ---
|
||||||
|
You can render Mermaid diagrams to SVG using the pre-installed mmdc CLI.
|
||||||
|
The puppeteer config (no-sandbox + Chromium path) is at /root/.puppeteerrc.json.
|
||||||
|
|
||||||
|
1) Write a .mmd file with your diagram:
|
||||||
|
cat > /tmp/diagram.mmd << 'EOF'
|
||||||
|
graph TD
|
||||||
|
A[Start] --> B[End]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
2) Render to SVG (the -p flag is required):
|
||||||
|
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.svg -p /root/.puppeteerrc.json
|
||||||
|
|
||||||
|
3) Upload to R2:
|
||||||
|
aws s3 cp /tmp/diagram.svg
|
||||||
|
"s3://$(printenv R2_BUCKET)/$(git rev-parse --abbrev-ref HEAD)/diagram.svg"
|
||||||
|
--endpoint-url "$(printenv R2_ENDPOINT)"
|
||||||
|
|
||||||
|
4) The public URL will be:
|
||||||
|
$(printenv R2_PUBLIC_URL)/<branch>/diagram.svg
|
||||||
|
|
||||||
|
5) Include in PR descriptions using markdown image syntax.
|
||||||
|
|
||||||
|
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
|
||||||
|
|
||||||
|
linkedRepos:
|
||||||
|
- repo: windmill-labs/windmill-ee-private
|
||||||
|
alias: ee
|
||||||
@@ -67,7 +67,6 @@ files:
|
|||||||
copy:
|
copy:
|
||||||
- backend/.env
|
- backend/.env
|
||||||
- scripts/
|
- scripts/
|
||||||
- wm-ts-nav/target/release/wm-ts-nav
|
|
||||||
|
|
||||||
sandbox:
|
sandbox:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
117
CHANGELOG.md
117
CHANGELOG.md
@@ -1,122 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [1.657.0](https://github.com/windmill-labs/windmill/compare/v1.656.0...v1.657.0) (2026-03-14)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add datatable config support to CLI settings sync and backend export ([#8024](https://github.com/windmill-labs/windmill/issues/8024)) ([5df37fb](https://github.com/windmill-labs/windmill/commit/5df37fb0dbf9190a430f066cf2d3c48914782e53))
|
|
||||||
|
|
||||||
## [1.656.0](https://github.com/windmill-labs/windmill/compare/v1.655.0...v1.656.0) (2026-03-13)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add GitHub Enterprise Server (GHES) support for GitHub App git sync ([#8344](https://github.com/windmill-labs/windmill/issues/8344)) ([2e430c4](https://github.com/windmill-labs/windmill/commit/2e430c4c0b8540df7b6997434a7a9f9134858026))
|
|
||||||
* **cli:** add unified generate-metadata command ([#8335](https://github.com/windmill-labs/windmill/issues/8335)) ([4c2c165](https://github.com/windmill-labs/windmill/commit/4c2c165a5b757bd5f2f49074bb290407bce3b2fb))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **ci:** add NODE_AUTH_TOKEN for npm publish authentication ([2a8e276](https://github.com/windmill-labs/windmill/commit/2a8e276b6d2761bb2798b6bc5f8d90ab34fbb403))
|
|
||||||
* **ci:** remove provenance flag and use NPM_TOKEN for npm publish ([44dd3ee](https://github.com/windmill-labs/windmill/commit/44dd3ee8cd05d288828d1d46c84cbcdf40f8fa78))
|
|
||||||
* **cli:** exclude raw app backend files from script metadata generation ([#8362](https://github.com/windmill-labs/windmill/issues/8362)) ([060687b](https://github.com/windmill-labs/windmill/commit/060687b1fa6b627a7b06fbdc4b3f4eb0b63411c0))
|
|
||||||
* **cli:** normalize path separators in generate-metadata folder filter for Windows ([#8358](https://github.com/windmill-labs/windmill/issues/8358)) ([404ae09](https://github.com/windmill-labs/windmill/commit/404ae09d429fb545610ba17d747e1903c542d4a3))
|
|
||||||
* **cli:** suppress verbose lock generation messages in generate-metadata ([#8357](https://github.com/windmill-labs/windmill/issues/8357)) ([51933be](https://github.com/windmill-labs/windmill/commit/51933be3cabd853960d384cd358c7bcaef6bfa86))
|
|
||||||
* **frontend:** collapse flow topbar buttons to icon-only in narrow panes ([#8322](https://github.com/windmill-labs/windmill/issues/8322)) ([b585dee](https://github.com/windmill-labs/windmill/commit/b585dee64dfd63d20812ca969b17ff9ee9989493))
|
|
||||||
* **frontend:** filter webhook/email tokens by scope instead of label ([#8363](https://github.com/windmill-labs/windmill/issues/8363)) ([0d31c35](https://github.com/windmill-labs/windmill/commit/0d31c35f3e12d637c757a95fe350294002cbf640))
|
|
||||||
* **frontend:** improve native mode alert message and fix workspaced tag detection ([#8361](https://github.com/windmill-labs/windmill/issues/8361)) ([fb12b31](https://github.com/windmill-labs/windmill/commit/fb12b31df081b2f1ac63becea6e6538ca80f8c46))
|
|
||||||
* **frontend:** prevent duplicate and reserved agent tool names ([#8367](https://github.com/windmill-labs/windmill/issues/8367)) ([c431053](https://github.com/windmill-labs/windmill/commit/c431053a1e24ef29cd551a86de4d013fd7f158be))
|
|
||||||
* graceful shutdown instead of panic on job completion channel failure ([#8345](https://github.com/windmill-labs/windmill/issues/8345)) ([724d135](https://github.com/windmill-labs/windmill/commit/724d1350d070fcf078034a52166d3048fb74e6f3))
|
|
||||||
* Linked resources and vars not triggering both sync jobs on delete ([#8342](https://github.com/windmill-labs/windmill/issues/8342)) ([8e3b8bd](https://github.com/windmill-labs/windmill/commit/8e3b8bdfd2ded9652bc7e876c6bcd0ac2cfae148))
|
|
||||||
* lower default indexer memory/batch settings to prevent OOM ([#8347](https://github.com/windmill-labs/windmill/issues/8347)) ([d9d45cf](https://github.com/windmill-labs/windmill/commit/d9d45cf2f9235b0e7118d0fc97ccdc0776ca9726))
|
|
||||||
|
|
||||||
## [1.655.0](https://github.com/windmill-labs/windmill/compare/v1.654.0...v1.655.0) (2026-03-12)
|
|
||||||
|
|
||||||
|
|
||||||
### 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)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add indexer time window setting (default 7 days) ([#8290](https://github.com/windmill-labs/windmill/issues/8290)) ([0c4d72c](https://github.com/windmill-labs/windmill/commit/0c4d72cfe38d61cf3f6e9bc31056005f1adb494d))
|
|
||||||
* add slack connection fields to workspace settings export/import ([#8287](https://github.com/windmill-labs/windmill/issues/8287)) ([39e77ec](https://github.com/windmill-labs/windmill/commit/39e77ecd002b41630fa8d146ee0f15369656acda))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* optimize job_stats storage for timestamps and zero-memory jobs ([#8289](https://github.com/windmill-labs/windmill/issues/8289)) ([2d8335d](https://github.com/windmill-labs/windmill/commit/2d8335dc43a7cb182eb5a058119d8b0be067cdfd))
|
|
||||||
|
|
||||||
## [1.652.0](https://github.com/windmill-labs/windmill/compare/v1.651.1...v1.652.0) (2026-03-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add secretKeyRef support for package registry and storage credentials ([#8275](https://github.com/windmill-labs/windmill/issues/8275)) ([73d27e9](https://github.com/windmill-labs/windmill/commit/73d27e92dd6ced1602f6328f245fec0fa96860e1))
|
|
||||||
* expose OTEL trace context as env vars in job execution ([#8277](https://github.com/windmill-labs/windmill/issues/8277)) ([93f75ad](https://github.com/windmill-labs/windmill/commit/93f75ada5e49036f0d998e3d3d53de4dc2c2e83f))
|
|
||||||
* workflow-as-code (WAC) v2 ([#8172](https://github.com/windmill-labs/windmill/issues/8172)) ([a6d4390](https://github.com/windmill-labs/windmill/commit/a6d4390790d21d535df1e9d525bffd577c50d8dc))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* cli: support deleting linked resources-variables without throwing ([#8248](https://github.com/windmill-labs/windmill/issues/8248)) ([7859bca](https://github.com/windmill-labs/windmill/commit/7859bca6ae80d32a73a46910960afc6812e64115))
|
|
||||||
* Database studio fixes ([#8251](https://github.com/windmill-labs/windmill/issues/8251)) ([1d78589](https://github.com/windmill-labs/windmill/commit/1d785899404e8636a206cda9a2914df32a1a5269))
|
|
||||||
* **frontend:** unsaved changes dialog when flow already saved ([#8259](https://github.com/windmill-labs/windmill/issues/8259)) ([0330993](https://github.com/windmill-labs/windmill/commit/0330993cb66cdabffcd6e552a0f85a9a3931c62d))
|
|
||||||
* gracefully handle uninitialized OTEL tracing proxy port ([#8274](https://github.com/windmill-labs/windmill/issues/8274)) ([8b1fe8f](https://github.com/windmill-labs/windmill/commit/8b1fe8f9de7b0c03655558d0c46cfff71a4b2047))
|
|
||||||
* guard iteration picker VirtualList against empty items array ([#8273](https://github.com/windmill-labs/windmill/issues/8273)) ([c97cf60](https://github.com/windmill-labs/windmill/commit/c97cf604ab4a902d89fe873b90dbeb9dabc940eb)), closes [#8272](https://github.com/windmill-labs/windmill/issues/8272)
|
|
||||||
* mask secrets in OAuth config debug/log output ([#8269](https://github.com/windmill-labs/windmill/issues/8269)) ([e75763d](https://github.com/windmill-labs/windmill/commit/e75763dbe5ffe08e6cde082203596d510c2c3b29))
|
|
||||||
* parallel branchall hang on bad stop_after_all_iters_if + results.x.length null ([#8276](https://github.com/windmill-labs/windmill/issues/8276)) ([41e523f](https://github.com/windmill-labs/windmill/commit/41e523f827c4e3d5db525a1f14e24936b0b8af46))
|
|
||||||
* redact secrets in set_global_setting log line ([#8270](https://github.com/windmill-labs/windmill/issues/8270)) ([6a0473c](https://github.com/windmill-labs/windmill/commit/6a0473c5783dc0fef2ae82dc5345a5f0596f124d))
|
|
||||||
* remove $bindable() fallback values causing props_invalid_value error in oauth settings ([#8265](https://github.com/windmill-labs/windmill/issues/8265)) ([037035e](https://github.com/windmill-labs/windmill/commit/037035e094937827305dad29bd76a495d78bc46f))
|
|
||||||
* skip down migrations in potentially_stale checksum comparison ([#8271](https://github.com/windmill-labs/windmill/issues/8271)) ([5ba4029](https://github.com/windmill-labs/windmill/commit/5ba4029d8692b2e6054fca7f45ed4cfded4738ef))
|
|
||||||
* sql input horizontal scroll missing after switching flow steps ([#8249](https://github.com/windmill-labs/windmill/issues/8249)) ([ce8ac9c](https://github.com/windmill-labs/windmill/commit/ce8ac9cf52dc17061673b9b72556279c48c26f8e))
|
|
||||||
* wmill workspace whoami output ([#8246](https://github.com/windmill-labs/windmill/issues/8246)) ([1ac391a](https://github.com/windmill-labs/windmill/commit/1ac391a795585747fe5911ac41b157556569fedb))
|
|
||||||
|
|
||||||
## [1.651.1](https://github.com/windmill-labs/windmill/compare/v1.651.0...v1.651.1) (2026-03-05)
|
## [1.651.1](https://github.com/windmill-labs/windmill/compare/v1.651.0...v1.651.1) (2026-03-05)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
55
CLAUDE.md
55
CLAUDE.md
@@ -4,7 +4,7 @@ Open-source platform for internal tools, workflows, API integrations, background
|
|||||||
|
|
||||||
## Workflow
|
## 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
|
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`)
|
3. **Execute**: Follow coding patterns from skills (`rust-backend`, `svelte-frontend`)
|
||||||
4. **Validate**: After every change, run the appropriate checks per `docs/validation.md`
|
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
|
- **Validation**: `docs/validation.md` — what checks to run based on what you changed
|
||||||
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
|
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
|
||||||
- **Backend patterns**: use the `rust-backend` skill when writing Rust code
|
- **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.
|
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code
|
||||||
- **Code review**: use `/local-review` to review a PR for bugs and CLAUDE.md compliance
|
|
||||||
- **Domain guides**: `.claude/skills/native-trigger/` and `frontend/tutorial-system-guide.mdc`
|
- **Domain guides**: `.claude/skills/native-trigger/` and `frontend/tutorial-system-guide.mdc`
|
||||||
- **Brand/UI guidelines**: `frontend/brand-guidelines.md`
|
- **Brand/UI guidelines**: `frontend/brand-guidelines.md`
|
||||||
|
|
||||||
@@ -27,58 +26,8 @@ Open-source platform for internal tools, workflows, API integrations, background
|
|||||||
- **Login**: `admin@windmill.dev` / `changeme`
|
- **Login**: `admin@windmill.dev` / `changeme`
|
||||||
- **Instance settings**: navigate to `/#superadmin-settings`
|
- **Instance settings**: navigate to `/#superadmin-settings`
|
||||||
|
|
||||||
## Banned Patterns
|
|
||||||
|
|
||||||
### `$bindable(default_value)` on optional props
|
|
||||||
|
|
||||||
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()
|
|
||||||
```
|
|
||||||
|
|
||||||
**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)
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
## 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
|
- Search for existing code to reuse before writing new code
|
||||||
- Follow established patterns in the codebase
|
- Follow established patterns in the codebase
|
||||||
- Keep changes focused — don't refactor beyond what's asked
|
- Keep changes focused — don't refactor beyond what's asked
|
||||||
|
|||||||
@@ -268,11 +268,11 @@ RUN bun install -g windmill-cli \
|
|||||||
RUN curl -fsSL https://claude.ai/install.sh | bash \
|
RUN curl -fsSL https://claude.ai/install.sh | bash \
|
||||||
&& cp /root/.local/share/claude/versions/* /usr/bin/claude
|
&& 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=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
|
||||||
COPY --from=composer:2.9.5 /usr/bin/composer /usr/bin/composer
|
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
# add the docker client to call docker from a worker if enabled
|
# 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 RUSTUP_HOME="/tmp/windmill/cache/rustup"
|
||||||
ENV CARGO_HOME="/tmp/windmill/cache/cargo"
|
ENV CARGO_HOME="/tmp/windmill/cache/cargo"
|
||||||
|
|||||||
@@ -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.
|
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
|
## 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.
|
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.
|
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
|
## Login
|
||||||
|
|
||||||
Default credentials: `admin@windmill.dev` / `changeme`
|
Default credentials: `admin@windmill.dev` / `changeme`
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -46,11 +46,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
true,
|
false,
|
||||||
true,
|
false,
|
||||||
true,
|
false,
|
||||||
true,
|
false,
|
||||||
true,
|
false,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"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 ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"VarcharArray",
|
|
||||||
"JsonbArray",
|
|
||||||
"Varchar",
|
|
||||||
"Bool",
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"Bool",
|
|
||||||
"Varchar",
|
|
||||||
"Varchar",
|
|
||||||
"Text",
|
|
||||||
"Text",
|
|
||||||
"Varchar",
|
|
||||||
"Jsonb",
|
|
||||||
"Jsonb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04"
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE job_stats SET offsets_cs = array_append(offsets_cs, (EXTRACT(EPOCH FROM (now() - timeseries_start)) * 100)::int), timeseries_int = array_append(timeseries_int, $4) WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text",
|
|
||||||
"Uuid",
|
|
||||||
"Text",
|
|
||||||
"Int4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "0af0e0a1dddeee2021ba060e390e1b60caa3752669636e9fb0817a68121a9451"
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_queue SET canceled_by = $2, canceled_reason = $3 WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid",
|
|
||||||
"Varchar",
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "0cd9cad7109340edc81a5a40620b6efdae570e3416ec6c2493cc04f75c32a699"
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n SELECT j.id, j.runnable_path, j.args, j.kind::text AS \"kind!\"\n FROM v2_job j\n JOIN v2_job_queue q ON j.id = q.id\n WHERE j.runnable_path = $1\n AND j.kind = 'deploymentcallback'\n ORDER BY j.created_at DESC\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Uuid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "runnable_path",
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "args",
|
|
||||||
"type_info": "Jsonb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "kind!",
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "0d4f28ca0c5697c96711ca7225a9a4013e6ccabb495c371471c9d1287defda8f"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -12,11 +12,6 @@
|
|||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"name": "account_id",
|
"name": "account_id",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "github_base_url",
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -25,10 +20,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
null,
|
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "ae7adc583cdd3f876164ed60569ed531b05eaa17fccc599306eb1a96a65ee761"
|
"hash": "0ee14619dd81df460b2b8cc6df2b89646279f77469c35deffca8e17a11d7f6c8"
|
||||||
}
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_queue SET suspend = $2, suspend_until = now() + interval '14 day' WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid",
|
|
||||||
"Int4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "10af387fce25f6ea7af275e8e93b7ab1f2fc29a2ba79a39576551bdf66b592b6"
|
|
||||||
}
|
|
||||||
14
backend/.sqlx/query-16c96166ffa6b9aec65c6072b204b52b87e3c2f3d76e47eb173fc78721355066.json
generated
Normal file
14
backend/.sqlx/query-16c96166ffa6b9aec65c6072b204b52b87e3c2f3d76e47eb173fc78721355066.json
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n WITH _ AS (\n UPDATE debounce_key\n SET debounced_times = 0, -- reset debounced_times\n first_started_at = now(), -- rest\n previous_job_id = NULL\n WHERE job_id = $1\n )\n UPDATE v2_job_debounce_batch \n SET debounce_batch = nextval('debounce_batch_seq') -- move to new batch\n WHERE id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Uuid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "16c96166ffa6b9aec65c6072b204b52b87e3c2f3d76e47eb173fc78721355066"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1\n )\n SELECT\n q.id AS \"id!\",\n s.flow_status,\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n j.permissioned_as_email AS email,\n (ji.kind IN ('flow', 'flowpreview')) AS \"is_flow_level!\",\n (ji.kind NOT IN ('flow', 'flowpreview') AND q.id = ji.id) AS \"is_wac!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview') THEN ji.id\n ELSE COALESCE(ji.parent_job, ji.id)\n END\n JOIN v2_job j ON j.id = q.id\n LEFT JOIN v2_job_status s ON s.id = q.id\n FOR UPDATE OF q\n ",
|
"query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1\n )\n SELECT\n q.id AS \"id!\",\n s.flow_status,\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n j.permissioned_as_email AS email,\n (ji.kind IN ('flow', 'flowpreview')) AS \"is_flow_level!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview') THEN ji.id\n ELSE ji.parent_job\n END\n JOIN v2_job j ON j.id = q.id\n JOIN v2_job_status s ON s.id = q.id\n FOR UPDATE OF q\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -32,11 +32,6 @@
|
|||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"name": "is_flow_level!",
|
"name": "is_flow_level!",
|
||||||
"type_info": "Bool"
|
"type_info": "Bool"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 6,
|
|
||||||
"name": "is_wac!",
|
|
||||||
"type_info": "Bool"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -50,9 +45,8 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "dbc7e74e259b502e700491ee0248e0c9c8c61e1bf609be60ac5dc5d438189353"
|
"hash": "1a0ab65bbf2751f702fc696c1e32a7dd9524cdd806be1ad8e9ab88d4c88d3f82"
|
||||||
}
|
}
|
||||||
17
backend/.sqlx/query-1db82007445ff5f644bb607aa28f5747cb50d193475fff5fcfdde37d1bc74636.json
generated
Normal file
17
backend/.sqlx/query-1db82007445ff5f644bb607aa28f5747cb50d193475fff5fcfdde37d1bc74636.json
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE job_stats SET timestamps = array_append(timestamps, now()), timeseries_int = array_append(timeseries_int, $4) WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Uuid",
|
||||||
|
"Text",
|
||||||
|
"Int4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "1db82007445ff5f644bb607aa28f5747cb50d193475fff5fcfdde37d1bc74636"
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
16
backend/.sqlx/query-56f7325e3b0316866714e76d94b50d9d258c288883b2b5b0ab286f5cb50850b5.json
generated
Normal file
16
backend/.sqlx/query-56f7325e3b0316866714e76d94b50d9d258c288883b2b5b0ab286f5cb50850b5.json
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE v2_job_status SET\n workflow_as_code_status = jsonb_set(\n jsonb_set(\n COALESCE(workflow_as_code_status, '{}'::jsonb),\n array[$1],\n COALESCE(workflow_as_code_status->$1, '{}'::jsonb)\n ),\n array[$1, 'duration_ms'],\n to_jsonb($2::bigint)\n )\n WHERE id = $3",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int8",
|
||||||
|
"Uuid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "56f7325e3b0316866714e76d94b50d9d258c288883b2b5b0ab286f5cb50850b5"
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
null
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -73,5 +73,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "9229d9a9ff389cf26e480b604b83900e2d362ee934ef27284ef39f4eed440e59"
|
"hash": "72d3ebb05ac1ffeb0e8d0a3146d95bb5b90e7c4d1dc2c8a6ef06eddf6678f230"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"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": {
|
"parameters": {
|
||||||
@@ -57,14 +42,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "4cf4be7a981173d3f242887d9313c7e60d23e9827f23c0de5b546ed56697d54a"
|
"hash": "7e3bfb33fb771aec39b43a7550091ce7c9b1261b52d10f4a7f3273fed3c916df"
|
||||||
}
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_queue SET suspend = GREATEST(suspend - 1, 0) WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a35164456ade8e79cb8f5418c8fe82c45be45409f881292f0d4a3316362ba1f4"
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_queue SET running = false, started_at = null WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a684f160d1a366c1928fef27c613e6e08f808f423c8f2d58b9c849aba7d176f5"
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_queue SET suspend = 0, suspend_until = NULL WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a72081cb042f09034338dcb49381e91093233cf16af0dae666b4743f3878b22e"
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE job_stats SET offsets_cs = array_append(offsets_cs, (EXTRACT(EPOCH FROM (now() - timeseries_start)) * 100)::int), timeseries_float = array_append(timeseries_float, $4) WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text",
|
|
||||||
"Uuid",
|
|
||||||
"Text",
|
|
||||||
"Float4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a837494a58ab58bfa18c0385350a861076a5fc4f7eefceb1c0de5cf55293f327"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"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": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -29,5 +29,5 @@
|
|||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
"hash": "fbccafe6d34093a723b9d5a6ee8d618a80ceba6de2d39202e6293ef5207c31f6"
|
"hash": "ad8487a797713b3a6c10fb399c9fb8dcd940bb92e998145e250f28ccfe1c7033"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"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 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, now(), $12, $13, $14\n )\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -13,8 +13,6 @@
|
|||||||
"JsonbArray",
|
"JsonbArray",
|
||||||
"Varchar",
|
"Varchar",
|
||||||
"Bool",
|
"Bool",
|
||||||
"Varchar",
|
|
||||||
"Bool",
|
|
||||||
{
|
{
|
||||||
"Custom": {
|
"Custom": {
|
||||||
"name": "trigger_mode",
|
"name": "trigger_mode",
|
||||||
@@ -36,5 +34,5 @@
|
|||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
"hash": "5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f"
|
"hash": "aed5439aa6dad950e505f9f8f6914fa5ca21319c501b2822c9bc751ddfc9a0a4"
|
||||||
}
|
}
|
||||||
16
backend/.sqlx/query-b4d48c820bf41619bffa8f62367e98369e1d93514e1618723a34bf96080d4ebc.json
generated
Normal file
16
backend/.sqlx/query-b4d48c820bf41619bffa8f62367e98369e1d93514e1618723a34bf96080d4ebc.json
generated
Normal 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"
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_queue SET suspend = GREATEST(suspend - 1, 0) WHERE id = $1 RETURNING suspend",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "suspend",
|
|
||||||
"type_info": "Int4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "b663e6baf2f8da00c6d94e5b8e35be9d9f51071978c0e48df4284d8b90000a4a"
|
|
||||||
}
|
|
||||||
22
backend/.sqlx/query-be00ac55e8668a0ed3befda7d8595c7cda0cba0b119d4fdb8a0dea1b28a1d560.json
generated
Normal file
22
backend/.sqlx/query-be00ac55e8668a0ed3befda7d8595c7cda0cba0b119d4fdb8a0dea1b28a1d560.json
generated
Normal 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"
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
15
backend/.sqlx/query-bf2aeb9a1e649106d2a084c1d628690a44573c1869a206474811215714ba97c2.json
generated
Normal file
15
backend/.sqlx/query-bf2aeb9a1e649106d2a084c1d628690a44573c1869a206474811215714ba97c2.json
generated
Normal 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"
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_status SET\n workflow_as_code_status = jsonb_set(\n jsonb_set(\n workflow_as_code_status,\n array[$1],\n COALESCE(workflow_as_code_status->$1, '{}'::jsonb)\n ),\n array[$1, 'duration_ms'],\n to_jsonb($2::bigint)\n )\n WHERE id = $3 AND workflow_as_code_status IS NOT NULL\n RETURNING workflow_as_code_status->'_checkpoint'->'pending_steps'->'job_ids' AS \"job_ids: serde_json::Value\"",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "job_ids: serde_json::Value",
|
|
||||||
"type_info": "Jsonb"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text",
|
|
||||||
"Int8",
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
null
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "c331609952e0b98d36f605bd5d2933aa54523bf44d63e304440e53b9eadd5340"
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "SELECT args FROM v2_job WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "args",
|
|
||||||
"type_info": "Jsonb"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "c944e384c4b4c6455b431978adc54d176f294b661675392cf92561c0e6e02e6e"
|
|
||||||
}
|
|
||||||
14
backend/.sqlx/query-c9530931f670eab1208c4a284a55afdc3fcbb0eb5f98fd63e2ec89442becbfaa.json
generated
Normal file
14
backend/.sqlx/query-c9530931f670eab1208c4a284a55afdc3fcbb0eb5f98fd63e2ec89442becbfaa.json
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n WITH _ AS (\n UPDATE debounce_key\n SET debounced_times = 0,\n first_started_at = now(),\n previous_job_id = NULL\n WHERE job_id = $1\n )\n UPDATE v2_job_debounce_batch\n SET debounce_batch = nextval('debounce_batch_seq')\n WHERE id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Uuid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "c9530931f670eab1208c4a284a55afdc3fcbb0eb5f98fd63e2ec89442becbfaa"
|
||||||
|
}
|
||||||
17
backend/.sqlx/query-d44c37882150532383d1058639f4d3af288a346c50f29809dbc55a34088a0abc.json
generated
Normal file
17
backend/.sqlx/query-d44c37882150532383d1058639f4d3af288a346c50f29809dbc55a34088a0abc.json
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE job_stats SET timestamps = array_append(timestamps, now()), timeseries_float = array_append(timeseries_float, $4) WHERE workspace_id = $1 AND job_id = $2 AND metric_id = $3",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Uuid",
|
||||||
|
"Text",
|
||||||
|
"Float4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "d44c37882150532383d1058639f4d3af288a346c50f29809dbc55a34088a0abc"
|
||||||
|
}
|
||||||
27
backend/.sqlx/query-e2921e44c70cf6c76c55177f2b56985e84c59ecb3e1a13fcf27d5f7ae5f8d84c.json
generated
Normal file
27
backend/.sqlx/query-e2921e44c70cf6c76c55177f2b56985e84c59ecb3e1a13fcf27d5f7ae5f8d84c.json
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"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 script_path = $5,\n path = $6,\n is_flow = $7,\n edited_by = $8,\n email = $9,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $12,\n error_handler_args = $13,\n retry = $14\n WHERE\n workspace_id = $10 AND path = $11\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"VarcharArray",
|
||||||
|
"JsonbArray",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Bool",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar",
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Varchar",
|
||||||
|
"Jsonb",
|
||||||
|
"Jsonb"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "e2921e44c70cf6c76c55177f2b56985e84c59ecb3e1a13fcf27d5f7ae5f8d84c"
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "UPDATE v2_job_queue SET suspend = 1, suspend_until = now() + make_interval(secs => $2) WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid",
|
|
||||||
"Float8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "f56c58fea9f27d2e55d33720e032808e90cb068d1048717f82992f476377cc20"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
313
backend/Cargo.lock
generated
313
backend/Cargo.lock
generated
@@ -169,9 +169,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "1.0.0"
|
version = "0.6.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse",
|
||||||
@@ -184,15 +184,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.14"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
version = "1.0.0"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
]
|
]
|
||||||
@@ -1860,9 +1860,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bon"
|
name = "bon"
|
||||||
version = "3.9.1"
|
version = "3.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe"
|
checksum = "2d13a61f2963b88eef9c1be03df65d42f6996dfeac1054870d950fcf66686f83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bon-macros",
|
"bon-macros",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
@@ -1870,9 +1870,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bon-macros"
|
name = "bon-macros"
|
||||||
version = "3.9.1"
|
version = "3.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c"
|
checksum = "d314cc62af2b6b0c65780555abb4d02a03dd3b799cd42419044f0c38d99738c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.23.0",
|
"darling 0.23.0",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
@@ -2208,9 +2208,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.57"
|
version = "1.2.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
@@ -2323,9 +2323,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.0"
|
version = "4.5.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -2333,9 +2333,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.6.0"
|
version = "4.5.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -2345,9 +2345,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.6.0"
|
version = "4.5.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2357,9 +2357,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "1.1.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
@@ -2418,9 +2418,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.5"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
@@ -7103,7 +7103,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.6.3",
|
"socket2 0.6.2",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -7974,9 +7974,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.183"
|
version = "0.2.182"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libffi"
|
name = "libffi"
|
||||||
@@ -8102,9 +8102,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libz-sys"
|
name = "libz-sys"
|
||||||
version = "1.1.25"
|
version = "1.1.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1"
|
checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -9383,9 +9383,9 @@ checksum = "80adb31078122c880307e9cdfd4e3361e6545c319f9b9dcafcb03acd3b51a575"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
@@ -9460,9 +9460,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.76"
|
version = "0.10.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
|
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -9507,9 +9507,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.112"
|
version = "0.9.111"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -10641,9 +10641,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick_cache"
|
name = "quick_cache"
|
||||||
version = "0.6.19"
|
version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "530e84778a55de0f52645a51d4e3b9554978acd6a1e7cd50b6a6784692b3029e"
|
checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"equivalent",
|
"equivalent",
|
||||||
@@ -10664,7 +10664,7 @@ dependencies = [
|
|||||||
"quinn-udp",
|
"quinn-udp",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"rustls 0.23.35",
|
"rustls 0.23.35",
|
||||||
"socket2 0.6.3",
|
"socket2 0.6.2",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -10673,9 +10673,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-proto"
|
name = "quinn-proto"
|
||||||
version = "0.11.14"
|
version = "0.11.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -10702,7 +10702,7 @@ dependencies = [
|
|||||||
"cfg_aliases 0.2.1",
|
"cfg_aliases 0.2.1",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2 0.6.3",
|
"socket2 0.6.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
@@ -11987,9 +11987,9 @@ checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.29"
|
version = "0.1.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -12677,12 +12677,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.3"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -13854,9 +13854,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.26.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
@@ -14462,7 +14462,7 @@ dependencies = [
|
|||||||
"indexmap 2.11.1",
|
"indexmap 2.11.1",
|
||||||
"toml_datetime 0.7.0",
|
"toml_datetime 0.7.0",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
"winnow 0.7.15",
|
"winnow 0.7.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -14471,7 +14471,7 @@ version = "1.0.9+spec-1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow 0.7.15",
|
"winnow 0.7.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -14697,9 +14697,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.23"
|
version = "0.3.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"matchers",
|
"matchers",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
@@ -15741,7 +15741,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill"
|
name = "windmill"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-nats",
|
"async-nats",
|
||||||
@@ -15808,7 +15808,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-alerting"
|
name = "windmill-alerting"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -15821,7 +15821,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api"
|
name = "windmill-api"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -15849,7 +15849,6 @@ dependencies = [
|
|||||||
"dashmap 6.1.0",
|
"dashmap 6.1.0",
|
||||||
"datafusion",
|
"datafusion",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"eventsource-stream",
|
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"git-version",
|
"git-version",
|
||||||
@@ -15941,7 +15940,6 @@ dependencies = [
|
|||||||
"windmill-parser-py",
|
"windmill-parser-py",
|
||||||
"windmill-parser-py-imports",
|
"windmill-parser-py-imports",
|
||||||
"windmill-parser-sql",
|
"windmill-parser-sql",
|
||||||
"windmill-parser-sql-asset",
|
|
||||||
"windmill-parser-ts",
|
"windmill-parser-ts",
|
||||||
"windmill-queue",
|
"windmill-queue",
|
||||||
"windmill-store",
|
"windmill-store",
|
||||||
@@ -15962,7 +15960,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-agent-workers"
|
name = "windmill-api-agent-workers"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -15985,7 +15983,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-assets"
|
name = "windmill-api-assets"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -15998,7 +15996,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-auth"
|
name = "windmill-api-auth"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -16024,7 +16022,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-client"
|
name = "windmill-api-client"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -16034,7 +16032,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-configs"
|
name = "windmill-api-configs"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16051,7 +16049,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-debug"
|
name = "windmill-api-debug"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -16074,7 +16072,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-embeddings"
|
name = "windmill-api-embeddings"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -16097,7 +16095,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-flow-conversations"
|
name = "windmill-api-flow-conversations"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16113,7 +16111,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-flows"
|
name = "windmill-api-flows"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16133,7 +16131,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-groups"
|
name = "windmill-api-groups"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16153,7 +16151,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-inputs"
|
name = "windmill-api-inputs"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16167,7 +16165,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-integration-tests"
|
name = "windmill-api-integration-tests"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-nats",
|
"async-nats",
|
||||||
@@ -16187,7 +16185,6 @@ dependencies = [
|
|||||||
"windmill-api-auth",
|
"windmill-api-auth",
|
||||||
"windmill-api-client",
|
"windmill-api-client",
|
||||||
"windmill-common",
|
"windmill-common",
|
||||||
"windmill-git-sync",
|
|
||||||
"windmill-native-triggers",
|
"windmill-native-triggers",
|
||||||
"windmill-test-utils",
|
"windmill-test-utils",
|
||||||
"windmill-worker",
|
"windmill-worker",
|
||||||
@@ -16195,7 +16192,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-jobs"
|
name = "windmill-api-jobs"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -16220,7 +16217,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-npm-proxy"
|
name = "windmill-api-npm-proxy"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"flate2",
|
"flate2",
|
||||||
@@ -16238,7 +16235,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-openapi"
|
name = "windmill-api-openapi"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -16259,7 +16256,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-schedule"
|
name = "windmill-api-schedule"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16279,7 +16276,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-scripts"
|
name = "windmill-api-scripts"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16309,7 +16306,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-settings"
|
name = "windmill-api-settings"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -16336,7 +16333,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-sse"
|
name = "windmill-api-sse"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -16348,7 +16345,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-users"
|
name = "windmill-api-users"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -16371,7 +16368,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-workers"
|
name = "windmill-api-workers"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16385,7 +16382,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-api-workspaces"
|
name = "windmill-api-workspaces"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -16416,7 +16413,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-audit"
|
name = "windmill-audit"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -16430,7 +16427,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-autoscaling"
|
name = "windmill-autoscaling"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -16449,7 +16446,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-common"
|
name = "windmill-common"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -16548,7 +16545,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-dep-map"
|
name = "windmill-dep-map"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
@@ -16567,7 +16564,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-git-sync"
|
name = "windmill-git-sync"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -16582,7 +16579,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-indexer"
|
name = "windmill-indexer"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"astral-tokio-tar",
|
"astral-tokio-tar",
|
||||||
@@ -16606,7 +16603,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-jseval"
|
name = "windmill-jseval"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -16623,7 +16620,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-macros"
|
name = "windmill-macros"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -16639,7 +16636,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-mcp"
|
name = "windmill-mcp"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -16660,7 +16657,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-native-triggers"
|
name = "windmill-native-triggers"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -16691,7 +16688,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-oauth"
|
name = "windmill-oauth"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-oauth2",
|
"async-oauth2",
|
||||||
@@ -16715,7 +16712,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-object-store"
|
name = "windmill-object-store"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@@ -16749,7 +16746,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-operator"
|
name = "windmill-operator"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -16767,7 +16764,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser"
|
name = "windmill-parser"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case 0.6.0",
|
"convert_case 0.6.0",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -16776,7 +16773,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-bash"
|
name = "windmill-parser-bash"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -16788,7 +16785,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-csharp"
|
name = "windmill-parser-csharp"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -16800,7 +16797,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-go"
|
name = "windmill-parser-go"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gosyn",
|
"gosyn",
|
||||||
@@ -16812,7 +16809,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-graphql"
|
name = "windmill-parser-graphql"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -16824,7 +16821,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-java"
|
name = "windmill-parser-java"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -16836,7 +16833,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-nu"
|
name = "windmill-parser-nu"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
@@ -16847,7 +16844,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-php"
|
name = "windmill-parser-php"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
@@ -16858,7 +16855,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-py"
|
name = "windmill-parser-py"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
@@ -16866,22 +16863,12 @@ dependencies = [
|
|||||||
"rustpython-parser",
|
"rustpython-parser",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"windmill-parser",
|
"windmill-parser",
|
||||||
]
|
"windmill-parser-sql",
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windmill-parser-py-asset"
|
|
||||||
version = "1.657.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"rustpython-ast",
|
|
||||||
"rustpython-parser",
|
|
||||||
"windmill-parser",
|
|
||||||
"windmill-parser-sql-asset",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-py-imports"
|
name = "windmill-parser-py-imports"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -16905,7 +16892,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-ruby"
|
name = "windmill-parser-ruby"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -16919,7 +16906,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-rust"
|
name = "windmill-parser-rust"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"convert_case 0.6.0",
|
"convert_case 0.6.0",
|
||||||
@@ -16936,7 +16923,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-sql"
|
name = "windmill-parser-sql"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -16944,17 +16931,6 @@ dependencies = [
|
|||||||
"regex-lite",
|
"regex-lite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"windmill-parser",
|
|
||||||
"windmill-types",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windmill-parser-sql-asset"
|
|
||||||
version = "1.657.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sqlparser 0.59.0",
|
"sqlparser 0.59.0",
|
||||||
"windmill-parser",
|
"windmill-parser",
|
||||||
"windmill-types",
|
"windmill-types",
|
||||||
@@ -16962,7 +16938,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-ts"
|
name = "windmill-parser-ts"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -16976,43 +16952,12 @@ dependencies = [
|
|||||||
"triomphe",
|
"triomphe",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windmill-parser",
|
"windmill-parser",
|
||||||
]
|
"windmill-parser-sql",
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windmill-parser-ts-asset"
|
|
||||||
version = "1.657.0"
|
|
||||||
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",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windmill-parser-wac"
|
|
||||||
version = "1.657.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"rustpython-ast",
|
|
||||||
"rustpython-parser",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2 0.10.9",
|
|
||||||
"swc_common",
|
|
||||||
"swc_ecma_ast",
|
|
||||||
"swc_ecma_parser",
|
|
||||||
"swc_ecma_visit",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-parser-yaml"
|
name = "windmill-parser-yaml"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -17023,7 +16968,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-queue"
|
name = "windmill-queue"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -17060,7 +17005,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-runtime-nativets"
|
name = "windmill-runtime-nativets"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"const_format",
|
"const_format",
|
||||||
@@ -17098,7 +17043,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-sql-datatype-parser-wasm"
|
name = "windmill-sql-datatype-parser-wasm"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -17109,7 +17054,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-store"
|
name = "windmill-store"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -17138,7 +17083,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-test-utils"
|
name = "windmill-test-utils"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
@@ -17161,7 +17106,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger"
|
name = "windmill-trigger"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17194,7 +17139,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-email"
|
name = "windmill-trigger-email"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17214,7 +17159,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-gcp"
|
name = "windmill-trigger-gcp"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17248,7 +17193,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-http"
|
name = "windmill-trigger-http"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17283,7 +17228,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-kafka"
|
name = "windmill-trigger-kafka"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17306,7 +17251,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-mqtt"
|
name = "windmill-trigger-mqtt"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17330,7 +17275,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-nats"
|
name = "windmill-trigger-nats"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-nats",
|
"async-nats",
|
||||||
@@ -17354,7 +17299,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-postgres"
|
name = "windmill-trigger-postgres"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17389,7 +17334,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-sqs"
|
name = "windmill-trigger-sqs"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17417,7 +17362,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-trigger-websocket"
|
name = "windmill-trigger-websocket"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -17440,7 +17385,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-types"
|
name = "windmill-types"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
@@ -17458,7 +17403,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-worker"
|
name = "windmill-worker"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-once-cell",
|
"async-once-cell",
|
||||||
@@ -17564,7 +17509,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windmill-worker-volumes"
|
name = "windmill-worker-volumes"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -18164,9 +18109,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.15"
|
version = "0.7.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -18447,18 +18392,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.42"
|
version = "0.8.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
|
checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.42"
|
version = "0.8.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
|
checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "windmill"
|
name = "windmill"
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
@@ -59,7 +59,6 @@ members = [
|
|||||||
"./windmill-oauth",
|
"./windmill-oauth",
|
||||||
"./parsers/windmill-parser",
|
"./parsers/windmill-parser",
|
||||||
"./parsers/windmill-parser-ts",
|
"./parsers/windmill-parser-ts",
|
||||||
"./parsers/windmill-parser-ts-asset",
|
|
||||||
"./parsers/windmill-parser-go",
|
"./parsers/windmill-parser-go",
|
||||||
"./parsers/windmill-parser-rust",
|
"./parsers/windmill-parser-rust",
|
||||||
"./parsers/windmill-parser-csharp",
|
"./parsers/windmill-parser-csharp",
|
||||||
@@ -68,11 +67,7 @@ members = [
|
|||||||
"./parsers/windmill-parser-ruby",
|
"./parsers/windmill-parser-ruby",
|
||||||
"./parsers/windmill-parser-bash",
|
"./parsers/windmill-parser-bash",
|
||||||
"./parsers/windmill-parser-py",
|
"./parsers/windmill-parser-py",
|
||||||
"./parsers/windmill-parser-py-asset",
|
|
||||||
"./parsers/windmill-parser-py-imports",
|
"./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-sql-datatype-parser-wasm",
|
||||||
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu",
|
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu",
|
||||||
"./windmill-worker-volumes",
|
"./windmill-worker-volumes",
|
||||||
@@ -82,7 +77,7 @@ members = [
|
|||||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.657.0"
|
version = "1.651.1"
|
||||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -324,9 +319,7 @@ windmill-api-workers = { path = "./windmill-api-workers" }
|
|||||||
windmill-store = { path = "./windmill-store" }
|
windmill-store = { path = "./windmill-store" }
|
||||||
windmill-parser = { path = "./parsers/windmill-parser" }
|
windmill-parser = { path = "./parsers/windmill-parser" }
|
||||||
windmill-parser-ts = { path = "./parsers/windmill-parser-ts" }
|
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 = { 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-py-imports = { path = "./parsers/windmill-parser-py-imports" }
|
||||||
windmill-parser-go = { path = "./parsers/windmill-parser-go" }
|
windmill-parser-go = { path = "./parsers/windmill-parser-go" }
|
||||||
windmill-parser-rust = { path = "./parsers/windmill-parser-rust" }
|
windmill-parser-rust = { path = "./parsers/windmill-parser-rust" }
|
||||||
@@ -337,10 +330,8 @@ windmill-parser-ruby = { path = "./parsers/windmill-parser-ruby" }
|
|||||||
windmill-parser-nu = { path = "./parsers/windmill-parser-nu" }
|
windmill-parser-nu = { path = "./parsers/windmill-parser-nu" }
|
||||||
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
|
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
|
||||||
windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
|
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-graphql = { path = "./parsers/windmill-parser-graphql" }
|
||||||
windmill-parser-php = { path = "./parsers/windmill-parser-php" }
|
windmill-parser-php = { path = "./parsers/windmill-parser-php" }
|
||||||
windmill-parser-wac = { path = "./parsers/windmill-parser-wac" }
|
|
||||||
windmill-jseval = { path = "./windmill-jseval" }
|
windmill-jseval = { path = "./windmill-jseval" }
|
||||||
windmill-runtime-nativets = { path = "./windmill-runtime-nativets" }
|
windmill-runtime-nativets = { path = "./windmill-runtime-nativets" }
|
||||||
windmill-api-client = { path = "./windmill-api-client" }
|
windmill-api-client = { path = "./windmill-api-client" }
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
c74c86b78a66b976fd9968b21f77903723e668ec
|
f9549c813b3dba5324ea9d1edacc8756a6d699bf
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE kafka_trigger DROP COLUMN auto_offset_reset;
|
|
||||||
ALTER TABLE kafka_trigger DROP COLUMN reset_offset;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE kafka_trigger ADD COLUMN auto_offset_reset VARCHAR(10) NOT NULL DEFAULT 'latest';
|
|
||||||
ALTER TABLE kafka_trigger ADD COLUMN reset_offset BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE job_stats DROP COLUMN IF EXISTS timeseries_start;
|
|
||||||
ALTER TABLE job_stats DROP COLUMN IF EXISTS offsets_cs;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
-- Store timeseries timestamps as a start time + integer centisecond offsets
|
|
||||||
-- instead of full TIMESTAMPTZ[] arrays. Saves ~4 bytes per data point.
|
|
||||||
-- i32 centiseconds gives ~248 days of range with 10ms precision.
|
|
||||||
ALTER TABLE job_stats ADD COLUMN IF NOT EXISTS timeseries_start TIMESTAMPTZ;
|
|
||||||
ALTER TABLE job_stats ADD COLUMN IF NOT EXISTS offsets_cs INTEGER[];
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS audit_partitioned CASCADE;
|
|
||||||
@@ -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);
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS kafka_pending_commits;
|
|
||||||
ALTER TABLE kafka_trigger DROP COLUMN auto_commit;
|
|
||||||
@@ -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);
|
|
||||||
@@ -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
|
|
||||||
@@ -10,6 +10,7 @@ path = "./src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
windmill-parser.workspace = true
|
windmill-parser.workspace = true
|
||||||
|
windmill-parser-sql.workspace = true
|
||||||
rustpython-parser.workspace = true
|
rustpython-parser.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ impl AssetsFinder {
|
|||||||
_ => return Err(()),
|
_ => return Err(()),
|
||||||
};
|
};
|
||||||
// We use the SQL parser to detect RW, specific tables, etc.
|
// 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,
|
*kind,
|
||||||
path,
|
path,
|
||||||
schema.as_deref(),
|
schema.as_deref(),
|
||||||
@@ -21,54 +21,13 @@ use rustpython_parser::{
|
|||||||
Parse,
|
Parse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod asset_parser;
|
||||||
pub mod pydantic_parser;
|
pub mod pydantic_parser;
|
||||||
|
|
||||||
|
pub use asset_parser::parse_assets;
|
||||||
|
|
||||||
const FUNCTION_CALL: &str = "<function call>";
|
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.
|
/// 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.
|
/// 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.
|
/// This avoids expensive parsing for the common case where scripts don't use these features.
|
||||||
@@ -337,14 +296,11 @@ pub fn parse_python_signature(
|
|||||||
|
|
||||||
// Check if main function was found
|
// Check if main function was found
|
||||||
if params.is_none() {
|
if params.is_none() {
|
||||||
let is_wac_v2 = (code.contains("@workflow") || code.contains("workflow("))
|
|
||||||
&& (code.contains("@task") || code.contains("task("))
|
|
||||||
&& (code.contains("import wmill") || code.contains("from wmill"));
|
|
||||||
return Ok(MainArgSignature {
|
return Ok(MainArgSignature {
|
||||||
star_args: false,
|
star_args: false,
|
||||||
star_kwargs: false,
|
star_kwargs: false,
|
||||||
args: vec![],
|
args: vec![],
|
||||||
no_main_func: Some(!is_wac_v2),
|
no_main_func: Some(true),
|
||||||
has_preprocessor: Some(has_preprocessor),
|
has_preprocessor: Some(has_preprocessor),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -434,19 +390,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 {
|
Arg {
|
||||||
otyp: union_otyp.or_else(|| {
|
otyp: metadata.descriptions.get(&arg_name).map(|d| d.to_string()),
|
||||||
metadata.descriptions.get(&arg_name).map(|d| d.to_string())
|
|
||||||
}),
|
|
||||||
name: arg_name,
|
name: arg_name,
|
||||||
typ,
|
typ,
|
||||||
has_default: has_default || default.is_some(),
|
has_default: has_default || default.is_some(),
|
||||||
@@ -496,9 +441,6 @@ fn parse_expr(
|
|||||||
Expr::Constant(ExprConstant { value: Constant::None, .. })
|
Expr::Constant(ExprConstant { value: Constant::None, .. })
|
||||||
) {
|
) {
|
||||||
(parse_expr(&x.left, enums, module).0, true)
|
(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 {
|
} else {
|
||||||
(Typ::Unknown, false)
|
(Typ::Unknown, false)
|
||||||
}
|
}
|
||||||
@@ -1104,33 +1046,6 @@ def main(a: str, b: Optional[str], c: str | None): return
|
|||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn test_parse_python_sig_enum() -> anyhow::Result<()> {
|
fn test_parse_python_sig_enum() -> anyhow::Result<()> {
|
||||||
let code = r#"
|
let code = r#"
|
||||||
|
|||||||
@@ -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"] }
|
|
||||||
@@ -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;
|
|
||||||
@@ -20,4 +20,5 @@ windmill-types.workspace = true
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
sqlparser = { version = "0.59.0", features = ["visitor"] }
|
||||||
@@ -20,6 +20,11 @@ pub use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ};
|
|||||||
pub const SANITIZED_ENUM_STR: &str = "__sanitized_enum__";
|
pub const SANITIZED_ENUM_STR: &str = "__sanitized_enum__";
|
||||||
pub const SANITIZED_RAW_STRING_STR: &str = "__sanitized_raw_string__";
|
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> {
|
pub fn parse_mysql_sig(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||||
let parsed = parse_mysql_file(&code)?;
|
let parsed = parse_mysql_file(&code)?;
|
||||||
if let Some(x) = parsed {
|
if let Some(x) = parsed {
|
||||||
@@ -233,7 +238,7 @@ lazy_static::lazy_static! {
|
|||||||
|
|
||||||
// used for `unsafe` sql interpolation
|
// used for `unsafe` sql interpolation
|
||||||
// -- %%name%% (type) = default
|
// -- %%name%% (type) = default
|
||||||
static ref RE_ARG_SQL_INTERPOLATION: Regex = Regex::new(r#"(?m)^--\s*%%([a-z_][a-z0-9_]*)%%[ \t]*([\w][\w \t\/]*)?(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
static ref RE_ARG_SQL_INTERPOLATION: Regex = Regex::new(r#"(?m)^--\s*%%([a-z_][a-z0-9_]*)%%\s*([\s\w\/]+)?(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parsed_default(parsed_typ: &Typ, default: String) -> Option<serde_json::Value> {
|
fn parsed_default(parsed_typ: &Typ, default: String) -> Option<serde_json::Value> {
|
||||||
@@ -1542,36 +1547,4 @@ SELECT $1::integer;
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_pgsql_safe_interpolated_args() -> anyhow::Result<()> {
|
|
||||||
// There was a bug where enum would be "angrycreative"/"bishop"/"test SELECT x"
|
|
||||||
let code = r#"
|
|
||||||
-- %%table_name%% angrycreative/bishop/test
|
|
||||||
SELECT x
|
|
||||||
"#;
|
|
||||||
assert_eq!(
|
|
||||||
parse_pgsql_sig(code)?,
|
|
||||||
MainArgSignature {
|
|
||||||
star_args: false,
|
|
||||||
star_kwargs: false,
|
|
||||||
args: vec![Arg {
|
|
||||||
otyp: Some("__sanitized_enum__".to_string()),
|
|
||||||
name: "table_name".to_string(),
|
|
||||||
typ: Typ::Str(Some(vec![
|
|
||||||
"angrycreative".to_string(),
|
|
||||||
"bishop".to_string(),
|
|
||||||
"test".to_string()
|
|
||||||
])),
|
|
||||||
default: None,
|
|
||||||
has_default: false,
|
|
||||||
oidx: None,
|
|
||||||
},],
|
|
||||||
no_main_func: None,
|
|
||||||
has_preprocessor: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -15,6 +15,7 @@ serde-wasm-bindgen.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
windmill-parser.workspace = true
|
windmill-parser.workspace = true
|
||||||
|
windmill-parser-sql.workspace = true
|
||||||
swc_common.workspace = true
|
swc_common.workspace = true
|
||||||
triomphe.workspace = true
|
triomphe.workspace = true
|
||||||
swc_ecma_parser.workspace = true
|
swc_ecma_parser.workspace = true
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ impl Visit for AssetsFinder {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// We use the SQL parser to detect RW, specific tables, etc.
|
// 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,
|
*kind,
|
||||||
asset_name,
|
asset_name,
|
||||||
schema.as_deref(),
|
schema.as_deref(),
|
||||||
@@ -211,6 +211,8 @@ pub enum TypeDecl {
|
|||||||
Interface(TsInterfaceDecl),
|
Interface(TsInterfaceDecl),
|
||||||
Alias(TsTypeAliasDecl),
|
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
|
/// skip_params is a micro optimization for when we just want to find the main
|
||||||
/// function without parsing all the params.
|
/// function without parsing all the params.
|
||||||
@@ -259,9 +261,7 @@ pub fn parse_deno_signature(
|
|||||||
for specifier in &named_export.specifiers {
|
for specifier in &named_export.specifiers {
|
||||||
if let swc_ecma_ast::ExportSpecifier::Named(spec) = specifier {
|
if let swc_ecma_ast::ExportSpecifier::Named(spec) = specifier {
|
||||||
let export_name = match &spec.exported {
|
let export_name = match &spec.exported {
|
||||||
Some(swc_ecma_ast::ModuleExportName::Ident(ident)) => {
|
Some(swc_ecma_ast::ModuleExportName::Ident(ident)) => ident.sym.as_ref(),
|
||||||
ident.sym.as_ref()
|
|
||||||
}
|
|
||||||
Some(swc_ecma_ast::ModuleExportName::Str(s)) => s.value.as_ref(),
|
Some(swc_ecma_ast::ModuleExportName::Str(s)) => s.value.as_ref(),
|
||||||
None => match &spec.orig {
|
None => match &spec.orig {
|
||||||
swc_ecma_ast::ModuleExportName::Ident(ident) => ident.sym.as_ref(),
|
swc_ecma_ast::ModuleExportName::Ident(ident) => ident.sym.as_ref(),
|
||||||
@@ -315,11 +315,7 @@ pub fn parse_deno_signature(
|
|||||||
|
|
||||||
let mut c: u16 = 0;
|
let mut c: u16 = 0;
|
||||||
|
|
||||||
let is_wac_v2 = entrypoint_params.is_none()
|
let no_main_func = entrypoint_params.is_none();
|
||||||
&& code.contains("workflow(")
|
|
||||||
&& code.contains("task(")
|
|
||||||
&& code.contains("windmill-client");
|
|
||||||
let no_main_func = entrypoint_params.is_none() && !is_wac_v2;
|
|
||||||
let mut type_resolver = HashMap::new();
|
let mut type_resolver = HashMap::new();
|
||||||
let r = MainArgSignature {
|
let r = MainArgSignature {
|
||||||
star_args: false,
|
star_args: false,
|
||||||
@@ -363,12 +359,8 @@ fn parse_param(
|
|||||||
let r = match param.pat {
|
let r = match param.pat {
|
||||||
Pat::Ident(ident) => {
|
Pat::Ident(ident) => {
|
||||||
let (name, typ, nullable) = binding_ident_to_arg(symbol_table, type_resolver, &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 {
|
Ok(Arg {
|
||||||
otyp,
|
otyp: None,
|
||||||
name,
|
name,
|
||||||
typ,
|
typ,
|
||||||
default: None,
|
default: None,
|
||||||
@@ -378,21 +370,13 @@ fn parse_param(
|
|||||||
}
|
}
|
||||||
// Pat::Object(ObjectPat { ... }) = todo!()
|
// Pat::Object(ObjectPat { ... }) = todo!()
|
||||||
Pat::Assign(AssignPat { left, right, .. }) => {
|
Pat::Assign(AssignPat { left, right, .. }) => {
|
||||||
let (name, mut typ, _nullable, otyp) = match *left {
|
let (name, mut typ, _nullable) = match *left {
|
||||||
Pat::Ident(ident) => {
|
Pat::Ident(ident) => 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));
|
|
||||||
let (name, typ, nullable) =
|
|
||||||
binding_ident_to_arg(symbol_table, type_resolver, &ident);
|
|
||||||
(name, typ, nullable, otyp)
|
|
||||||
}
|
|
||||||
Pat::Object(ObjectPat { type_ann, .. }) => {
|
Pat::Object(ObjectPat { type_ann, .. }) => {
|
||||||
let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann);
|
let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann);
|
||||||
*counter += 1;
|
*counter += 1;
|
||||||
let name = format!("anon{}", counter);
|
let name = format!("anon{}", counter);
|
||||||
(name, typ, nullable, None)
|
(name, typ, nullable)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
@@ -428,7 +412,7 @@ fn parse_param(
|
|||||||
if typ == Typ::Unknown && dflt.is_some() {
|
if typ == Typ::Unknown && dflt.is_some() {
|
||||||
typ = json_to_typ(dflt.as_ref().unwrap(), false);
|
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, .. }) => {
|
Pat::Object(ObjectPat { type_ann, .. }) => {
|
||||||
let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann);
|
let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann);
|
||||||
@@ -849,9 +833,7 @@ fn tstype_to_typ(
|
|||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
symbol @ _ if symbol.starts_with("DynMultiselect_") => (
|
symbol @ _ if symbol.starts_with("DynMultiselect_") => (
|
||||||
Typ::DynMultiselect(
|
Typ::DynMultiselect(symbol.strip_prefix("DynMultiselect_").unwrap().to_string()),
|
||||||
symbol.strip_prefix("DynMultiselect_").unwrap().to_string(),
|
|
||||||
),
|
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
symbol @ _ => {
|
symbol @ _ => {
|
||||||
@@ -973,75 +955,6 @@ fn one_of_properties(
|
|||||||
.collect()
|
.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> {
|
fn find_undefined(types: &Vec<Box<TsType>>) -> Option<usize> {
|
||||||
types.into_iter().position(|x| match **x {
|
types.into_iter().position(|x| match **x {
|
||||||
TsType::TsKeywordType(TsKeywordType { kind, .. }) => {
|
TsType::TsKeywordType(TsKeywordType { kind, .. }) => {
|
||||||
|
|||||||
@@ -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]
|
#[test]
|
||||||
fn test_parse_invalid_typescript() {
|
fn test_parse_invalid_typescript() {
|
||||||
let code = r#"
|
let code = r#"
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "windmill-parser-wac"
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
authors.workspace = true
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "windmill_parser_wac"
|
|
||||||
path = "./src/lib.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rustpython-parser.workspace = true
|
|
||||||
rustpython-ast = { version = "0.4.0", features = ["visitor"] }
|
|
||||||
swc_common.workspace = true
|
|
||||||
swc_ecma_parser.workspace = true
|
|
||||||
swc_ecma_ast.workspace = true
|
|
||||||
swc_ecma_visit.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
anyhow.workspace = true
|
|
||||||
sha2.workspace = true
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct WorkflowDag {
|
|
||||||
pub nodes: Vec<DagNode>,
|
|
||||||
pub edges: Vec<DagEdge>,
|
|
||||||
pub params: Vec<Param>,
|
|
||||||
pub source_hash: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Param {
|
|
||||||
pub name: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub typ: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct DagNode {
|
|
||||||
pub id: String,
|
|
||||||
pub node_type: DagNodeType,
|
|
||||||
pub label: String,
|
|
||||||
pub line: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum DagNodeType {
|
|
||||||
Step { name: String, script: String },
|
|
||||||
Branch { condition_source: String },
|
|
||||||
ParallelStart,
|
|
||||||
ParallelEnd,
|
|
||||||
LoopStart { iter_source: String },
|
|
||||||
LoopEnd,
|
|
||||||
Return,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct DagEdge {
|
|
||||||
pub from: String,
|
|
||||||
pub to: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub label: Option<String>,
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
pub mod dag;
|
|
||||||
pub mod python;
|
|
||||||
pub mod typescript;
|
|
||||||
pub mod validation;
|
|
||||||
|
|
||||||
use dag::WorkflowDag;
|
|
||||||
use validation::CompileError;
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum ParseResult {
|
|
||||||
#[serde(rename = "success")]
|
|
||||||
Success(WorkflowDag),
|
|
||||||
#[serde(rename = "error")]
|
|
||||||
Error { errors: Vec<CompileError> },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_workflow(code: &str, language: &str) -> ParseResult {
|
|
||||||
let result = match language {
|
|
||||||
"python" | "python3" | "py" => python::parse_python_workflow(code),
|
|
||||||
"typescript" | "ts" | "deno" | "bun" => typescript::parse_ts_workflow(code),
|
|
||||||
_ => Err(vec![CompileError {
|
|
||||||
message: format!("Unsupported language: {language}"),
|
|
||||||
line: 0,
|
|
||||||
}]),
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(dag) => ParseResult::Success(dag),
|
|
||||||
Err(errors) => ParseResult::Error { errors },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,717 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use rustpython_parser::{
|
|
||||||
ast::{
|
|
||||||
Expr, ExprAwait, ExprCall, ExprName, Stmt, StmtExpr, StmtFor, StmtIf, StmtReturn, StmtTry,
|
|
||||||
StmtTryStar, StmtWhile,
|
|
||||||
},
|
|
||||||
Parse,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::dag::{DagEdge, DagNode, DagNodeType, Param, WorkflowDag};
|
|
||||||
use crate::validation::{self, CompileError};
|
|
||||||
|
|
||||||
struct LineIndex {
|
|
||||||
newline_offsets: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineIndex {
|
|
||||||
fn new(source: &str) -> Self {
|
|
||||||
let mut offsets = vec![0];
|
|
||||||
for (i, c) in source.char_indices() {
|
|
||||||
if c == '\n' {
|
|
||||||
offsets.push(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self { newline_offsets: offsets }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_of(&self, byte_offset: usize) -> usize {
|
|
||||||
match self.newline_offsets.binary_search(&byte_offset) {
|
|
||||||
Ok(line) => line + 1,
|
|
||||||
Err(line) => line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps task function name → optional external path (from `@task(path="...")`)
|
|
||||||
type TaskFunctions = HashMap<String, Option<String>>;
|
|
||||||
|
|
||||||
/// First pass: scan top-level `@task async def foo(...)` declarations.
|
|
||||||
fn collect_task_functions(stmts: &[Stmt]) -> TaskFunctions {
|
|
||||||
let mut tasks = HashMap::new();
|
|
||||||
for stmt in stmts {
|
|
||||||
if let Stmt::AsyncFunctionDef(func) = stmt {
|
|
||||||
for dec in &func.decorator_list {
|
|
||||||
match dec {
|
|
||||||
// @task (bare decorator)
|
|
||||||
Expr::Name(ExprName { id, .. }) if id.as_str() == "task" => {
|
|
||||||
tasks.insert(func.name.to_string(), None);
|
|
||||||
}
|
|
||||||
// @task(path="...")
|
|
||||||
Expr::Call(call) => {
|
|
||||||
if let Expr::Name(ExprName { id, .. }) = call.func.as_ref() {
|
|
||||||
if id.as_str() == "task" {
|
|
||||||
let path = extract_task_path_kwarg(call);
|
|
||||||
tasks.insert(func.name.to_string(), path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tasks
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the `path=` keyword argument from a `@task(path="...")` call.
|
|
||||||
fn extract_task_path_kwarg(call: &ExprCall) -> Option<String> {
|
|
||||||
for kw in &call.keywords {
|
|
||||||
if let Some(ref arg) = kw.arg {
|
|
||||||
if arg.as_str() == "path" {
|
|
||||||
if let Expr::Constant(c) = &kw.value {
|
|
||||||
if let rustpython_parser::ast::Constant::Str(s) = &c.value {
|
|
||||||
return Some(s.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WacWalker {
|
|
||||||
nodes: Vec<DagNode>,
|
|
||||||
edges: Vec<DagEdge>,
|
|
||||||
errors: Vec<CompileError>,
|
|
||||||
node_counter: usize,
|
|
||||||
line_index: LineIndex,
|
|
||||||
task_functions: TaskFunctions,
|
|
||||||
in_try: bool,
|
|
||||||
in_while: bool,
|
|
||||||
in_nested_func: bool,
|
|
||||||
in_comprehension: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WacWalker {
|
|
||||||
fn new(source: &str, task_functions: TaskFunctions) -> Self {
|
|
||||||
Self {
|
|
||||||
nodes: Vec::new(),
|
|
||||||
edges: Vec::new(),
|
|
||||||
errors: Vec::new(),
|
|
||||||
node_counter: 0,
|
|
||||||
line_index: LineIndex::new(source),
|
|
||||||
task_functions,
|
|
||||||
in_try: false,
|
|
||||||
in_while: false,
|
|
||||||
in_nested_func: false,
|
|
||||||
in_comprehension: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_id(&mut self) -> String {
|
|
||||||
let id = format!("step_{}", self.node_counter);
|
|
||||||
self.node_counter += 1;
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_node(&mut self, node: DagNode) -> String {
|
|
||||||
let id = node.id.clone();
|
|
||||||
self.nodes.push(node);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_edge(&mut self, from: &str, to: &str, label: Option<String>) {
|
|
||||||
self.edges
|
|
||||||
.push(DagEdge { from: from.to_string(), to: to.to_string(), label });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_of_expr(&self, expr: &Expr) -> usize {
|
|
||||||
let offset = match expr {
|
|
||||||
Expr::Call(c) => c.range.start().to_usize(),
|
|
||||||
Expr::Await(a) => a.range.start().to_usize(),
|
|
||||||
Expr::Attribute(a) => a.range.start().to_usize(),
|
|
||||||
Expr::Name(n) => n.range.start().to_usize(),
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
self.line_index.line_of(offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_of_stmt(&self, stmt: &Stmt) -> usize {
|
|
||||||
let offset = match stmt {
|
|
||||||
Stmt::If(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::For(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::While(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::Return(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::Expr(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::Try(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::TryStar(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::Assign(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::AnnAssign(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::FunctionDef(s) => s.range.start().to_usize(),
|
|
||||||
Stmt::AsyncFunctionDef(s) => s.range.start().to_usize(),
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
self.line_index.line_of(offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if an expression is a call to a known @task function
|
|
||||||
fn is_task_fn_call(&self, expr: &Expr) -> bool {
|
|
||||||
if let Expr::Call(call) = expr {
|
|
||||||
if let Expr::Name(ExprName { id, .. }) = call.func.as_ref() {
|
|
||||||
return self.task_functions.contains_key(id.as_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if an expression is `asyncio.gather(...)` call
|
|
||||||
fn is_asyncio_gather_call(expr: &Expr) -> bool {
|
|
||||||
if let Expr::Call(call) = expr {
|
|
||||||
if let Expr::Attribute(rustpython_parser::ast::ExprAttribute { value, attr, .. }) =
|
|
||||||
call.func.as_ref()
|
|
||||||
{
|
|
||||||
if attr.as_str() == "gather" {
|
|
||||||
if let Expr::Name(ExprName { id, .. }) = value.as_ref() {
|
|
||||||
return id.as_str() == "asyncio";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract step name and script from a task function call.
|
|
||||||
/// Name = function name, script = task_path or function name.
|
|
||||||
fn extract_step_info_from_task_call(&self, call: &ExprCall) -> Option<(String, String)> {
|
|
||||||
if let Expr::Name(ExprName { id, .. }) = call.func.as_ref() {
|
|
||||||
let name = id.to_string();
|
|
||||||
let script = self
|
|
||||||
.task_functions
|
|
||||||
.get(id.as_str())
|
|
||||||
.and_then(|p| p.clone())
|
|
||||||
.unwrap_or_else(|| name.clone());
|
|
||||||
Some((name, script))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expr_to_source(expr: &Expr) -> String {
|
|
||||||
match expr {
|
|
||||||
Expr::Compare(c) => {
|
|
||||||
let left = Self::expr_to_source(&c.left);
|
|
||||||
if let Some(comparator) = c.comparators.first() {
|
|
||||||
let right = Self::expr_to_source(comparator);
|
|
||||||
let op = match c.ops.first() {
|
|
||||||
Some(rustpython_parser::ast::CmpOp::Gt) => ">",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::Lt) => "<",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::GtE) => ">=",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::LtE) => "<=",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::Eq) => "==",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::NotEq) => "!=",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::In) => "in",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::NotIn) => "not in",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::Is) => "is",
|
|
||||||
Some(rustpython_parser::ast::CmpOp::IsNot) => "is not",
|
|
||||||
None => "?",
|
|
||||||
};
|
|
||||||
format!("{left} {op} {right}")
|
|
||||||
} else {
|
|
||||||
left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::Subscript(s) => {
|
|
||||||
let value = Self::expr_to_source(&s.value);
|
|
||||||
let slice = Self::expr_to_source(&s.slice);
|
|
||||||
format!("{value}[{slice}]")
|
|
||||||
}
|
|
||||||
Expr::Attribute(a) => {
|
|
||||||
let value = Self::expr_to_source(&a.value);
|
|
||||||
format!("{value}.{}", a.attr)
|
|
||||||
}
|
|
||||||
Expr::Name(n) => n.id.to_string(),
|
|
||||||
Expr::Constant(c) => match &c.value {
|
|
||||||
rustpython_parser::ast::Constant::Str(s) => format!("\"{s}\""),
|
|
||||||
rustpython_parser::ast::Constant::Int(i) => i.to_string(),
|
|
||||||
rustpython_parser::ast::Constant::Float(f) => f.to_string(),
|
|
||||||
rustpython_parser::ast::Constant::Bool(b) => b.to_string(),
|
|
||||||
rustpython_parser::ast::Constant::None => "None".to_string(),
|
|
||||||
_ => "...".to_string(),
|
|
||||||
},
|
|
||||||
_ => "...".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a statement body contains any task function calls (recursively)
|
|
||||||
fn body_contains_step(&self, body: &[Stmt]) -> bool {
|
|
||||||
for stmt in body {
|
|
||||||
if self.stmt_contains_step(stmt) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stmt_contains_step(&self, stmt: &Stmt) -> bool {
|
|
||||||
match stmt {
|
|
||||||
Stmt::Expr(StmtExpr { value, .. }) => self.expr_contains_step(value),
|
|
||||||
Stmt::Assign(a) => self.expr_contains_step(&a.value),
|
|
||||||
Stmt::If(s) => self.body_contains_step(&s.body) || self.body_contains_step(&s.orelse),
|
|
||||||
Stmt::For(s) => self.body_contains_step(&s.body) || self.body_contains_step(&s.orelse),
|
|
||||||
Stmt::While(s) => {
|
|
||||||
self.body_contains_step(&s.body) || self.body_contains_step(&s.orelse)
|
|
||||||
}
|
|
||||||
Stmt::Try(s) => {
|
|
||||||
self.body_contains_step(&s.body)
|
|
||||||
|| self.body_contains_step(&s.orelse)
|
|
||||||
|| self.body_contains_step(&s.finalbody)
|
|
||||||
|| s.handlers.iter().any(|h| match h {
|
|
||||||
rustpython_parser::ast::ExceptHandler::ExceptHandler(eh) => {
|
|
||||||
self.body_contains_step(&eh.body)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Stmt::TryStar(s) => {
|
|
||||||
self.body_contains_step(&s.body)
|
|
||||||
|| self.body_contains_step(&s.orelse)
|
|
||||||
|| self.body_contains_step(&s.finalbody)
|
|
||||||
|| s.handlers.iter().any(|h| match h {
|
|
||||||
rustpython_parser::ast::ExceptHandler::ExceptHandler(eh) => {
|
|
||||||
self.body_contains_step(&eh.body)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Stmt::Return(_) => false,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expr_contains_step(&self, expr: &Expr) -> bool {
|
|
||||||
if self.is_task_fn_call(expr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
match expr {
|
|
||||||
Expr::Await(ExprAwait { value, .. }) => self.expr_contains_step(value),
|
|
||||||
Expr::Call(call) => {
|
|
||||||
if self.is_task_fn_call(&Expr::Call(call.clone())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if Self::is_asyncio_gather_call(&Expr::Call(call.clone())) {
|
|
||||||
return call.args.iter().any(|a| self.expr_contains_step(a));
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk a list of statements, returning (first_node_id, last_node_id)
|
|
||||||
fn walk_body(&mut self, body: &[Stmt]) -> Option<(String, String)> {
|
|
||||||
let mut first_id: Option<String> = None;
|
|
||||||
let mut prev_id: Option<String> = None;
|
|
||||||
|
|
||||||
for stmt in body {
|
|
||||||
if let Some((stmt_first, stmt_last)) = self.walk_stmt(stmt) {
|
|
||||||
if let Some(ref prev) = prev_id {
|
|
||||||
self.add_edge(prev, &stmt_first, None);
|
|
||||||
}
|
|
||||||
if first_id.is_none() {
|
|
||||||
first_id = Some(stmt_first);
|
|
||||||
}
|
|
||||||
prev_id = Some(stmt_last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match (first_id, prev_id) {
|
|
||||||
(Some(f), Some(l)) => Some((f, l)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_stmt(&mut self, stmt: &Stmt) -> Option<(String, String)> {
|
|
||||||
match stmt {
|
|
||||||
Stmt::Expr(StmtExpr { value, .. }) => self.walk_expr_stmt(value),
|
|
||||||
Stmt::Assign(a) => self.walk_expr_stmt(&a.value),
|
|
||||||
Stmt::If(if_stmt) => self.walk_if(if_stmt),
|
|
||||||
Stmt::For(for_stmt) => self.walk_for(for_stmt),
|
|
||||||
Stmt::While(while_stmt) => self.walk_while(while_stmt),
|
|
||||||
Stmt::Try(try_stmt) => self.walk_try(try_stmt),
|
|
||||||
Stmt::TryStar(try_stmt) => self.walk_try_star(try_stmt),
|
|
||||||
Stmt::Return(ret) => self.walk_return(ret),
|
|
||||||
Stmt::FunctionDef(_) | Stmt::AsyncFunctionDef(_) => {
|
|
||||||
if self.stmt_contains_step(stmt) {
|
|
||||||
self.errors.push(validation::error_step_in_nested_function(
|
|
||||||
self.line_of_stmt(stmt),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_expr_stmt(&mut self, expr: &Expr) -> Option<(String, String)> {
|
|
||||||
// await task_fn(...)
|
|
||||||
if let Expr::Await(ExprAwait { value, .. }) = expr {
|
|
||||||
// await task_fn(...)
|
|
||||||
if let Expr::Call(call) = value.as_ref() {
|
|
||||||
if self.is_task_fn_call(&Expr::Call(call.clone())) {
|
|
||||||
return self.emit_step(call, expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// await asyncio.gather(task_fn(...), task_fn(...), ...)
|
|
||||||
if Self::is_asyncio_gather_call(value) {
|
|
||||||
if let Expr::Call(gather_call) = value.as_ref() {
|
|
||||||
return self.emit_parallel(gather_call, expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bare task_fn() without await — validation error
|
|
||||||
if self.is_task_fn_call(expr) {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_missing_await(self.line_of_expr(expr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_step(&mut self, call: &ExprCall, expr: &Expr) -> Option<(String, String)> {
|
|
||||||
if self.in_try {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_try(self.line_of_expr(expr)));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if self.in_while {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_while(self.line_of_expr(expr)));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if self.in_nested_func {
|
|
||||||
self.errors.push(validation::error_step_in_nested_function(
|
|
||||||
self.line_of_expr(expr),
|
|
||||||
));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if self.in_comprehension {
|
|
||||||
self.errors.push(validation::error_step_in_comprehension(
|
|
||||||
self.line_of_expr(expr),
|
|
||||||
));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (name, script) = self
|
|
||||||
.extract_step_info_from_task_call(call)
|
|
||||||
.unwrap_or(("unknown".into(), "unknown".into()));
|
|
||||||
let id = self.next_id();
|
|
||||||
let node_id = self.add_node(DagNode {
|
|
||||||
id: id.clone(),
|
|
||||||
node_type: DagNodeType::Step { name: name.clone(), script },
|
|
||||||
label: name,
|
|
||||||
line: self.line_of_expr(expr),
|
|
||||||
});
|
|
||||||
Some((node_id.clone(), node_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_parallel(&mut self, gather_call: &ExprCall, expr: &Expr) -> Option<(String, String)> {
|
|
||||||
if self.in_try {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_try(self.line_of_expr(expr)));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if self.in_while {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_while(self.line_of_expr(expr)));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line = self.line_of_expr(expr);
|
|
||||||
let start_id = self.next_id();
|
|
||||||
let start_node_id = self.add_node(DagNode {
|
|
||||||
id: start_id.clone(),
|
|
||||||
node_type: DagNodeType::ParallelStart,
|
|
||||||
label: "parallel".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut step_ids = Vec::new();
|
|
||||||
for arg in &gather_call.args {
|
|
||||||
// Each arg should be task_fn(...)
|
|
||||||
if let Expr::Call(call) = arg {
|
|
||||||
if self.is_task_fn_call(&Expr::Call(call.clone())) {
|
|
||||||
let (name, script) = self
|
|
||||||
.extract_step_info_from_task_call(call)
|
|
||||||
.unwrap_or(("unknown".into(), "unknown".into()));
|
|
||||||
let step_id = self.next_id();
|
|
||||||
let node_id = self.add_node(DagNode {
|
|
||||||
id: step_id.clone(),
|
|
||||||
node_type: DagNodeType::Step { name: name.clone(), script },
|
|
||||||
label: name,
|
|
||||||
line: self.line_of_expr(arg),
|
|
||||||
});
|
|
||||||
self.add_edge(&start_node_id, &node_id, None);
|
|
||||||
step_ids.push(node_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let end_id = self.next_id();
|
|
||||||
let end_node_id = self.add_node(DagNode {
|
|
||||||
id: end_id.clone(),
|
|
||||||
node_type: DagNodeType::ParallelEnd,
|
|
||||||
label: "join".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
for step_id in &step_ids {
|
|
||||||
self.add_edge(step_id, &end_node_id, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((start_node_id, end_node_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_if(&mut self, if_stmt: &StmtIf) -> Option<(String, String)> {
|
|
||||||
let has_steps_in_body = self.body_contains_step(&if_stmt.body);
|
|
||||||
let has_steps_in_else = self.body_contains_step(&if_stmt.orelse);
|
|
||||||
|
|
||||||
if !has_steps_in_body && !has_steps_in_else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line = self.line_index.line_of(if_stmt.range.start().to_usize());
|
|
||||||
let condition_source = Self::expr_to_source(&if_stmt.test);
|
|
||||||
|
|
||||||
let branch_id = self.next_id();
|
|
||||||
let branch_node_id = self.add_node(DagNode {
|
|
||||||
id: branch_id.clone(),
|
|
||||||
node_type: DagNodeType::Branch { condition_source },
|
|
||||||
label: "if".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
let merge_id = format!("{branch_id}_merge");
|
|
||||||
|
|
||||||
let mut last_ids = Vec::new();
|
|
||||||
|
|
||||||
if let Some((true_first, true_last)) = self.walk_body(&if_stmt.body) {
|
|
||||||
self.add_edge(&branch_node_id, &true_first, Some("true".to_string()));
|
|
||||||
last_ids.push(true_last);
|
|
||||||
} else {
|
|
||||||
last_ids.push(branch_node_id.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !if_stmt.orelse.is_empty() {
|
|
||||||
if let Some((else_first, else_last)) = self.walk_body(&if_stmt.orelse) {
|
|
||||||
self.add_edge(&branch_node_id, &else_first, Some("false".to_string()));
|
|
||||||
last_ids.push(else_last);
|
|
||||||
} else {
|
|
||||||
last_ids.push(branch_node_id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if last_ids.len() == 1 {
|
|
||||||
Some((branch_node_id, last_ids.into_iter().next().unwrap()))
|
|
||||||
} else {
|
|
||||||
Some((branch_node_id, merge_id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_for(&mut self, for_stmt: &StmtFor) -> Option<(String, String)> {
|
|
||||||
if !self.body_contains_step(&for_stmt.body) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line = self.line_index.line_of(for_stmt.range.start().to_usize());
|
|
||||||
let iter_source = Self::expr_to_source(&for_stmt.iter);
|
|
||||||
|
|
||||||
let start_id = self.next_id();
|
|
||||||
let start_node_id = self.add_node(DagNode {
|
|
||||||
id: start_id.clone(),
|
|
||||||
node_type: DagNodeType::LoopStart { iter_source },
|
|
||||||
label: "for".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((body_first, body_last)) = self.walk_body(&for_stmt.body) {
|
|
||||||
self.add_edge(&start_node_id, &body_first, None);
|
|
||||||
self.add_edge(&body_last, &start_node_id, Some("next".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let end_id = self.next_id();
|
|
||||||
let end_node_id = self.add_node(DagNode {
|
|
||||||
id: end_id.clone(),
|
|
||||||
node_type: DagNodeType::LoopEnd,
|
|
||||||
label: "end for".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
self.add_edge(&start_node_id, &end_node_id, Some("done".to_string()));
|
|
||||||
|
|
||||||
Some((start_node_id, end_node_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_while(&mut self, while_stmt: &StmtWhile) -> Option<(String, String)> {
|
|
||||||
if self.body_contains_step(&while_stmt.body) {
|
|
||||||
let line = self.line_index.line_of(while_stmt.range.start().to_usize());
|
|
||||||
self.errors.push(validation::error_step_in_while(line));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_try(&mut self, try_stmt: &StmtTry) -> Option<(String, String)> {
|
|
||||||
let has_steps = self.body_contains_step(&try_stmt.body)
|
|
||||||
|| self.body_contains_step(&try_stmt.orelse)
|
|
||||||
|| self.body_contains_step(&try_stmt.finalbody)
|
|
||||||
|| try_stmt.handlers.iter().any(|h| match h {
|
|
||||||
rustpython_parser::ast::ExceptHandler::ExceptHandler(eh) => {
|
|
||||||
self.body_contains_step(&eh.body)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if has_steps {
|
|
||||||
let line = self.line_index.line_of(try_stmt.range.start().to_usize());
|
|
||||||
self.errors.push(validation::error_step_in_try(line));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_try_star(&mut self, try_stmt: &StmtTryStar) -> Option<(String, String)> {
|
|
||||||
let has_steps = self.body_contains_step(&try_stmt.body)
|
|
||||||
|| self.body_contains_step(&try_stmt.orelse)
|
|
||||||
|| self.body_contains_step(&try_stmt.finalbody)
|
|
||||||
|| try_stmt.handlers.iter().any(|h| match h {
|
|
||||||
rustpython_parser::ast::ExceptHandler::ExceptHandler(eh) => {
|
|
||||||
self.body_contains_step(&eh.body)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if has_steps {
|
|
||||||
let line = self.line_index.line_of(try_stmt.range.start().to_usize());
|
|
||||||
self.errors.push(validation::error_step_in_try(line));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_return(&mut self, ret: &StmtReturn) -> Option<(String, String)> {
|
|
||||||
let line = self.line_index.line_of(ret.range.start().to_usize());
|
|
||||||
let id = self.next_id();
|
|
||||||
let node_id = self.add_node(DagNode {
|
|
||||||
id: id.clone(),
|
|
||||||
node_type: DagNodeType::Return,
|
|
||||||
label: "return".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
Some((node_id.clone(), node_id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract workflow function parameters (no longer skips ctx)
|
|
||||||
fn extract_params(args: &rustpython_parser::ast::Arguments) -> Vec<Param> {
|
|
||||||
let mut params = Vec::new();
|
|
||||||
for arg_with_default in args.args.iter().chain(args.posonlyargs.iter()) {
|
|
||||||
let name = arg_with_default.def.arg.to_string();
|
|
||||||
let typ = arg_with_default
|
|
||||||
.def
|
|
||||||
.annotation
|
|
||||||
.as_ref()
|
|
||||||
.map(|ann| WacWalker::expr_to_source(ann));
|
|
||||||
params.push(Param { name, typ });
|
|
||||||
}
|
|
||||||
params
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_python_workflow(code: &str) -> Result<WorkflowDag, Vec<CompileError>> {
|
|
||||||
let ast = rustpython_parser::ast::Suite::parse(code, "<workflow>")
|
|
||||||
.map_err(|e| vec![CompileError { message: format!("Parse error: {e}"), line: 0 }])?;
|
|
||||||
|
|
||||||
// First pass: collect @task functions
|
|
||||||
let task_functions = collect_task_functions(&ast);
|
|
||||||
|
|
||||||
// Find the @workflow async def
|
|
||||||
let workflow_fn = ast.iter().find_map(|stmt| {
|
|
||||||
if let Stmt::AsyncFunctionDef(func) = stmt {
|
|
||||||
let has_workflow_decorator = func.decorator_list.iter().any(|dec| {
|
|
||||||
if let Expr::Name(ExprName { id, .. }) = dec {
|
|
||||||
id.as_str() == "workflow"
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if has_workflow_decorator {
|
|
||||||
return Some(func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Also check non-async for error reporting
|
|
||||||
if let Stmt::FunctionDef(func) = stmt {
|
|
||||||
let has_workflow_decorator = func.decorator_list.iter().any(|dec| {
|
|
||||||
if let Expr::Name(ExprName { id, .. }) = dec {
|
|
||||||
id.as_str() == "workflow"
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if has_workflow_decorator {
|
|
||||||
return None; // Will be reported as not-async below
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for non-async workflow function
|
|
||||||
let non_async_workflow = ast.iter().find_map(|stmt| {
|
|
||||||
if let Stmt::FunctionDef(func) = stmt {
|
|
||||||
let has_workflow_decorator = func.decorator_list.iter().any(|dec| {
|
|
||||||
if let Expr::Name(ExprName { id, .. }) = dec {
|
|
||||||
id.as_str() == "workflow"
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if has_workflow_decorator {
|
|
||||||
let line_index = LineIndex::new(code);
|
|
||||||
return Some(line_index.line_of(func.range.start().to_usize()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(line) = non_async_workflow {
|
|
||||||
if workflow_fn.is_none() {
|
|
||||||
return Err(vec![validation::error_not_async(line)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let workflow_fn = workflow_fn.ok_or_else(|| {
|
|
||||||
vec![CompileError { message: "No @workflow async function found.".to_string(), line: 0 }]
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let params = extract_params(&workflow_fn.args);
|
|
||||||
let source_hash = compute_source_hash(code);
|
|
||||||
|
|
||||||
let mut walker = WacWalker::new(code, task_functions);
|
|
||||||
walker.walk_body(&workflow_fn.body);
|
|
||||||
|
|
||||||
if !walker.errors.is_empty() {
|
|
||||||
return Err(walker.errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(WorkflowDag { nodes: walker.nodes, edges: walker.edges, params, source_hash })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_source_hash(code: &str) -> String {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(code.as_bytes());
|
|
||||||
format!("{:x}", hasher.finalize())
|
|
||||||
}
|
|
||||||
|
|
||||||
trait ToUsize {
|
|
||||||
fn to_usize(self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToUsize for rustpython_parser::text_size::TextSize {
|
|
||||||
fn to_usize(self) -> usize {
|
|
||||||
u32::from(self) as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,739 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use swc_common::{sync::Lrc, FileName, SourceMap, SourceMapper, Spanned};
|
|
||||||
use swc_ecma_ast::*;
|
|
||||||
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsSyntax};
|
|
||||||
|
|
||||||
use crate::dag::{DagEdge, DagNode, DagNodeType, Param, WorkflowDag};
|
|
||||||
use crate::validation::{self, CompileError};
|
|
||||||
|
|
||||||
/// Maps task function name → optional external path (from `task("f/path", ...)`)
|
|
||||||
type TaskFunctions = HashMap<String, Option<String>>;
|
|
||||||
|
|
||||||
/// First pass: scan top-level `const foo = task(async (...) => {})` or
|
|
||||||
/// `const foo = task("f/path", async (...) => {})` declarations.
|
|
||||||
fn collect_task_functions(module: &Module) -> TaskFunctions {
|
|
||||||
let mut tasks = HashMap::new();
|
|
||||||
for item in &module.body {
|
|
||||||
// const foo = task(async (...) => { ... })
|
|
||||||
// const foo = task("f/path", async (...) => { ... })
|
|
||||||
if let ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) = item {
|
|
||||||
for decl in &var_decl.decls {
|
|
||||||
if let (Some(name), Some(init)) = (extract_var_name(&decl.name), &decl.init) {
|
|
||||||
if let Some(path) = extract_task_call_info(init) {
|
|
||||||
tasks.insert(name, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// export const foo = task(...)
|
|
||||||
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) = item {
|
|
||||||
if let Decl::Var(var_decl) = &export.decl {
|
|
||||||
for decl in &var_decl.decls {
|
|
||||||
if let (Some(name), Some(init)) = (extract_var_name(&decl.name), &decl.init) {
|
|
||||||
if let Some(path) = extract_task_call_info(init) {
|
|
||||||
tasks.insert(name, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tasks
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract variable name from a pattern (simple ident case)
|
|
||||||
fn extract_var_name(pat: &Pat) -> Option<String> {
|
|
||||||
if let Pat::Ident(BindingIdent { id, .. }) = pat {
|
|
||||||
Some(id.sym.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if expr is `task(async fn)` or `task("path", async fn)`.
|
|
||||||
/// Returns Some(optional_path) if it is a task() call.
|
|
||||||
fn extract_task_call_info(expr: &Expr) -> Option<Option<String>> {
|
|
||||||
if let Expr::Call(call) = expr {
|
|
||||||
if let Callee::Expr(callee) = &call.callee {
|
|
||||||
if let Expr::Ident(ident) = callee.as_ref() {
|
|
||||||
if ident.sym.as_ref() == "task" {
|
|
||||||
// task("f/path", async fn) or task(async fn)
|
|
||||||
if call.args.len() == 2 {
|
|
||||||
// task("f/path", async fn)
|
|
||||||
let path = extract_string_lit(&call.args[0].expr);
|
|
||||||
return Some(path);
|
|
||||||
} else if call.args.len() == 1 {
|
|
||||||
// task(async fn)
|
|
||||||
return Some(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TsWacWalker {
|
|
||||||
nodes: Vec<DagNode>,
|
|
||||||
edges: Vec<DagEdge>,
|
|
||||||
errors: Vec<CompileError>,
|
|
||||||
node_counter: usize,
|
|
||||||
cm: Lrc<SourceMap>,
|
|
||||||
task_functions: TaskFunctions,
|
|
||||||
in_try: bool,
|
|
||||||
in_while: bool,
|
|
||||||
in_nested_func: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TsWacWalker {
|
|
||||||
fn new(cm: Lrc<SourceMap>, task_functions: TaskFunctions) -> Self {
|
|
||||||
Self {
|
|
||||||
nodes: Vec::new(),
|
|
||||||
edges: Vec::new(),
|
|
||||||
errors: Vec::new(),
|
|
||||||
node_counter: 0,
|
|
||||||
cm,
|
|
||||||
task_functions,
|
|
||||||
in_try: false,
|
|
||||||
in_while: false,
|
|
||||||
in_nested_func: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_id(&mut self) -> String {
|
|
||||||
let id = format!("step_{}", self.node_counter);
|
|
||||||
self.node_counter += 1;
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_node(&mut self, node: DagNode) -> String {
|
|
||||||
let id = node.id.clone();
|
|
||||||
self.nodes.push(node);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_edge(&mut self, from: &str, to: &str, label: Option<String>) {
|
|
||||||
self.edges
|
|
||||||
.push(DagEdge { from: from.to_string(), to: to.to_string(), label });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn span_line(&self, span: swc_common::Span) -> usize {
|
|
||||||
let loc = self.cm.lookup_char_pos(span.lo);
|
|
||||||
loc.line
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if expr is a call to a known task function
|
|
||||||
fn is_task_call(&self, expr: &Expr) -> bool {
|
|
||||||
if let Expr::Call(call) = expr {
|
|
||||||
if let Callee::Expr(callee) = &call.callee {
|
|
||||||
if let Expr::Ident(ident) = callee.as_ref() {
|
|
||||||
return self.task_functions.contains_key(ident.sym.as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if expr is `Promise.all([...])`
|
|
||||||
fn is_promise_all(expr: &Expr) -> bool {
|
|
||||||
if let Expr::Call(call) = expr {
|
|
||||||
if let Callee::Expr(callee) = &call.callee {
|
|
||||||
if let Expr::Member(MemberExpr { obj, prop: MemberProp::Ident(prop), .. }) =
|
|
||||||
callee.as_ref()
|
|
||||||
{
|
|
||||||
if prop.sym.as_ref() == "all" {
|
|
||||||
if let Expr::Ident(ident) = obj.as_ref() {
|
|
||||||
return ident.sym.as_ref() == "Promise";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract step name and script from a task function call.
|
|
||||||
/// Name = function name, script = task_path or function name.
|
|
||||||
fn extract_step_info_from_task_call(&self, call: &CallExpr) -> Option<(String, String)> {
|
|
||||||
if let Callee::Expr(callee) = &call.callee {
|
|
||||||
if let Expr::Ident(ident) = callee.as_ref() {
|
|
||||||
let name = ident.sym.to_string();
|
|
||||||
let script = self
|
|
||||||
.task_functions
|
|
||||||
.get(ident.sym.as_ref())
|
|
||||||
.and_then(|p| p.clone())
|
|
||||||
.unwrap_or_else(|| name.clone());
|
|
||||||
return Some((name, script));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expr_to_source(&self, expr: &Expr) -> String {
|
|
||||||
let span = expr.span();
|
|
||||||
self.cm
|
|
||||||
.span_to_snippet(span)
|
|
||||||
.unwrap_or_else(|_| "...".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn body_contains_step(&self, stmts: &[Stmt]) -> bool {
|
|
||||||
stmts.iter().any(|s| self.stmt_contains_step(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stmt_contains_step(&self, stmt: &Stmt) -> bool {
|
|
||||||
match stmt {
|
|
||||||
Stmt::Expr(expr_stmt) => self.expr_contains_step(&expr_stmt.expr),
|
|
||||||
Stmt::Decl(Decl::Var(var_decl)) => var_decl.decls.iter().any(|d| {
|
|
||||||
d.init
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |init| self.expr_contains_step(init))
|
|
||||||
}),
|
|
||||||
Stmt::If(if_stmt) => {
|
|
||||||
self.stmt_contains_step(&if_stmt.cons)
|
|
||||||
|| if_stmt
|
|
||||||
.alt
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |alt| self.stmt_contains_step(alt))
|
|
||||||
}
|
|
||||||
Stmt::Block(block) => self.body_contains_step(&block.stmts),
|
|
||||||
Stmt::For(for_stmt) => self.stmt_contains_step(&for_stmt.body),
|
|
||||||
Stmt::ForIn(for_in) => self.stmt_contains_step(&for_in.body),
|
|
||||||
Stmt::ForOf(for_of) => self.stmt_contains_step(&for_of.body),
|
|
||||||
Stmt::While(while_stmt) => self.stmt_contains_step(&while_stmt.body),
|
|
||||||
Stmt::Try(try_stmt) => {
|
|
||||||
self.body_contains_step(&try_stmt.block.stmts)
|
|
||||||
|| try_stmt
|
|
||||||
.handler
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |h| self.body_contains_step(&h.body.stmts))
|
|
||||||
|| try_stmt
|
|
||||||
.finalizer
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |f| self.body_contains_step(&f.stmts))
|
|
||||||
}
|
|
||||||
Stmt::Return(ret) => ret
|
|
||||||
.arg
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |arg| self.expr_contains_step(arg)),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expr_contains_step(&self, expr: &Expr) -> bool {
|
|
||||||
if self.is_task_call(expr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
match expr {
|
|
||||||
Expr::Await(await_expr) => self.expr_contains_step(&await_expr.arg),
|
|
||||||
Expr::Call(call) => {
|
|
||||||
if Self::is_promise_all(&Expr::Call(call.clone())) {
|
|
||||||
return call.args.iter().any(|a| self.expr_contains_step(&a.expr));
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Expr::Paren(p) => self.expr_contains_step(&p.expr),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_body(&mut self, stmts: &[Stmt]) -> Option<(String, String)> {
|
|
||||||
let mut first_id: Option<String> = None;
|
|
||||||
let mut prev_id: Option<String> = None;
|
|
||||||
|
|
||||||
for stmt in stmts {
|
|
||||||
if let Some((stmt_first, stmt_last)) = self.walk_stmt(stmt) {
|
|
||||||
if let Some(ref prev) = prev_id {
|
|
||||||
self.add_edge(prev, &stmt_first, None);
|
|
||||||
}
|
|
||||||
if first_id.is_none() {
|
|
||||||
first_id = Some(stmt_first);
|
|
||||||
}
|
|
||||||
prev_id = Some(stmt_last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match (first_id, prev_id) {
|
|
||||||
(Some(f), Some(l)) => Some((f, l)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_stmt(&mut self, stmt: &Stmt) -> Option<(String, String)> {
|
|
||||||
match stmt {
|
|
||||||
Stmt::Expr(expr_stmt) => self.walk_expr_stmt(&expr_stmt.expr),
|
|
||||||
Stmt::Decl(Decl::Var(var_decl)) => {
|
|
||||||
// const result = await task_fn(...)
|
|
||||||
for decl in &var_decl.decls {
|
|
||||||
if let Some(init) = &decl.init {
|
|
||||||
if let Some(result) = self.walk_expr_stmt(init) {
|
|
||||||
return Some(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Stmt::If(if_stmt) => self.walk_if(if_stmt),
|
|
||||||
Stmt::For(for_stmt) => self.walk_for_stmt(for_stmt),
|
|
||||||
Stmt::ForIn(for_in) => self.walk_for_in(for_in),
|
|
||||||
Stmt::ForOf(for_of) => self.walk_for_of(for_of),
|
|
||||||
Stmt::While(while_stmt) => self.walk_while(while_stmt),
|
|
||||||
Stmt::Try(try_stmt) => self.walk_try(try_stmt),
|
|
||||||
Stmt::Block(block) => self.walk_body(&block.stmts),
|
|
||||||
Stmt::Return(ret) => self.walk_return(ret),
|
|
||||||
Stmt::Decl(Decl::Fn(_)) => {
|
|
||||||
if self.stmt_contains_step(stmt) {
|
|
||||||
self.errors.push(validation::error_step_in_nested_function(
|
|
||||||
self.span_line(stmt.span()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_expr_stmt(&mut self, expr: &Expr) -> Option<(String, String)> {
|
|
||||||
// await task_fn(...)
|
|
||||||
if let Expr::Await(await_expr) = expr {
|
|
||||||
if let Expr::Call(call) = await_expr.arg.as_ref() {
|
|
||||||
if self.is_task_call(&Expr::Call(call.clone())) {
|
|
||||||
return self.emit_step(call, expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// await Promise.all([task_fn(...), ...])
|
|
||||||
if Self::is_promise_all(&await_expr.arg) {
|
|
||||||
if let Expr::Call(promise_call) = await_expr.arg.as_ref() {
|
|
||||||
return self.emit_parallel(promise_call, expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bare task_fn() without await
|
|
||||||
if self.is_task_call(expr) {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_missing_await(self.span_line(expr.span())));
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_step(&mut self, call: &CallExpr, expr: &Expr) -> Option<(String, String)> {
|
|
||||||
if self.in_try {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_catch(self.span_line(expr.span())));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if self.in_while {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_while(self.span_line(expr.span())));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if self.in_nested_func {
|
|
||||||
self.errors.push(validation::error_step_in_nested_function(
|
|
||||||
self.span_line(expr.span()),
|
|
||||||
));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (name, script) = self
|
|
||||||
.extract_step_info_from_task_call(call)
|
|
||||||
.unwrap_or(("unknown".into(), "unknown".into()));
|
|
||||||
let id = self.next_id();
|
|
||||||
let node_id = self.add_node(DagNode {
|
|
||||||
id: id.clone(),
|
|
||||||
node_type: DagNodeType::Step { name: name.clone(), script },
|
|
||||||
label: name,
|
|
||||||
line: self.span_line(expr.span()),
|
|
||||||
});
|
|
||||||
Some((node_id.clone(), node_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_parallel(&mut self, promise_call: &CallExpr, expr: &Expr) -> Option<(String, String)> {
|
|
||||||
if self.in_try {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_catch(self.span_line(expr.span())));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if self.in_while {
|
|
||||||
self.errors
|
|
||||||
.push(validation::error_step_in_while(self.span_line(expr.span())));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line = self.span_line(expr.span());
|
|
||||||
let start_id = self.next_id();
|
|
||||||
let start_node_id = self.add_node(DagNode {
|
|
||||||
id: start_id.clone(),
|
|
||||||
node_type: DagNodeType::ParallelStart,
|
|
||||||
label: "parallel".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut step_ids = Vec::new();
|
|
||||||
|
|
||||||
// Promise.all takes an array as first argument
|
|
||||||
if let Some(first_arg) = promise_call.args.first() {
|
|
||||||
if let Expr::Array(ArrayLit { elems, .. }) = first_arg.expr.as_ref() {
|
|
||||||
for elem in elems.iter().flatten() {
|
|
||||||
if let Expr::Call(call) = elem.expr.as_ref() {
|
|
||||||
if self.is_task_call(&Expr::Call(call.clone())) {
|
|
||||||
let (name, script) = self
|
|
||||||
.extract_step_info_from_task_call(call)
|
|
||||||
.unwrap_or(("unknown".into(), "unknown".into()));
|
|
||||||
let step_id = self.next_id();
|
|
||||||
let node_id = self.add_node(DagNode {
|
|
||||||
id: step_id.clone(),
|
|
||||||
node_type: DagNodeType::Step { name: name.clone(), script },
|
|
||||||
label: name,
|
|
||||||
line: self.span_line(elem.expr.span()),
|
|
||||||
});
|
|
||||||
self.add_edge(&start_node_id, &node_id, None);
|
|
||||||
step_ids.push(node_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let end_id = self.next_id();
|
|
||||||
let end_node_id = self.add_node(DagNode {
|
|
||||||
id: end_id.clone(),
|
|
||||||
node_type: DagNodeType::ParallelEnd,
|
|
||||||
label: "join".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
for step_id in &step_ids {
|
|
||||||
self.add_edge(step_id, &end_node_id, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((start_node_id, end_node_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_if(&mut self, if_stmt: &IfStmt) -> Option<(String, String)> {
|
|
||||||
let has_steps_cons = self.stmt_contains_step(&if_stmt.cons);
|
|
||||||
let has_steps_alt = if_stmt
|
|
||||||
.alt
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |a| self.stmt_contains_step(a));
|
|
||||||
|
|
||||||
if !has_steps_cons && !has_steps_alt {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line = self.span_line(if_stmt.span);
|
|
||||||
let condition_source = self.expr_to_source(&if_stmt.test);
|
|
||||||
|
|
||||||
let branch_id = self.next_id();
|
|
||||||
let branch_node_id = self.add_node(DagNode {
|
|
||||||
id: branch_id.clone(),
|
|
||||||
node_type: DagNodeType::Branch { condition_source },
|
|
||||||
label: "if".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut last_ids = Vec::new();
|
|
||||||
|
|
||||||
// True branch
|
|
||||||
if let Some((true_first, true_last)) = self.walk_stmt(&if_stmt.cons) {
|
|
||||||
self.add_edge(&branch_node_id, &true_first, Some("true".to_string()));
|
|
||||||
last_ids.push(true_last);
|
|
||||||
} else {
|
|
||||||
last_ids.push(branch_node_id.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// False branch
|
|
||||||
if let Some(alt) = &if_stmt.alt {
|
|
||||||
if let Some((else_first, else_last)) = self.walk_stmt(alt) {
|
|
||||||
self.add_edge(&branch_node_id, &else_first, Some("false".to_string()));
|
|
||||||
last_ids.push(else_last);
|
|
||||||
} else {
|
|
||||||
last_ids.push(branch_node_id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if last_ids.len() == 1 {
|
|
||||||
Some((branch_node_id, last_ids.into_iter().next().unwrap()))
|
|
||||||
} else {
|
|
||||||
let merge_id = format!("{branch_id}_merge");
|
|
||||||
Some((branch_node_id, merge_id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_for_stmt(&mut self, for_stmt: &ForStmt) -> Option<(String, String)> {
|
|
||||||
if !self.stmt_contains_step(&for_stmt.body) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.walk_loop_body(&for_stmt.body, for_stmt.span, "for")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_for_in(&mut self, for_in: &ForInStmt) -> Option<(String, String)> {
|
|
||||||
if !self.stmt_contains_step(&for_in.body) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let iter_source = self.expr_to_source(&for_in.right);
|
|
||||||
self.walk_loop_body_with_iter(&for_in.body, for_in.span, &iter_source)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_for_of(&mut self, for_of: &ForOfStmt) -> Option<(String, String)> {
|
|
||||||
if !self.stmt_contains_step(&for_of.body) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let iter_source = self.expr_to_source(&for_of.right);
|
|
||||||
self.walk_loop_body_with_iter(&for_of.body, for_of.span, &iter_source)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_loop_body(
|
|
||||||
&mut self,
|
|
||||||
body: &Stmt,
|
|
||||||
span: swc_common::Span,
|
|
||||||
_label: &str,
|
|
||||||
) -> Option<(String, String)> {
|
|
||||||
self.walk_loop_body_with_iter(body, span, "...")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_loop_body_with_iter(
|
|
||||||
&mut self,
|
|
||||||
body: &Stmt,
|
|
||||||
span: swc_common::Span,
|
|
||||||
iter_source: &str,
|
|
||||||
) -> Option<(String, String)> {
|
|
||||||
let line = self.span_line(span);
|
|
||||||
let start_id = self.next_id();
|
|
||||||
let start_node_id = self.add_node(DagNode {
|
|
||||||
id: start_id.clone(),
|
|
||||||
node_type: DagNodeType::LoopStart { iter_source: iter_source.to_string() },
|
|
||||||
label: "for".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((body_first, body_last)) = self.walk_stmt(body) {
|
|
||||||
self.add_edge(&start_node_id, &body_first, None);
|
|
||||||
self.add_edge(&body_last, &start_node_id, Some("next".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let end_id = self.next_id();
|
|
||||||
let end_node_id = self.add_node(DagNode {
|
|
||||||
id: end_id.clone(),
|
|
||||||
node_type: DagNodeType::LoopEnd,
|
|
||||||
label: "end for".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
self.add_edge(&start_node_id, &end_node_id, Some("done".to_string()));
|
|
||||||
|
|
||||||
Some((start_node_id, end_node_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_while(&mut self, while_stmt: &WhileStmt) -> Option<(String, String)> {
|
|
||||||
if self.stmt_contains_step(&while_stmt.body) {
|
|
||||||
self.errors.push(validation::error_step_in_while(
|
|
||||||
self.span_line(while_stmt.span),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_try(&mut self, try_stmt: &TryStmt) -> Option<(String, String)> {
|
|
||||||
let has_steps = self.body_contains_step(&try_stmt.block.stmts)
|
|
||||||
|| try_stmt
|
|
||||||
.handler
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |h| self.body_contains_step(&h.body.stmts))
|
|
||||||
|| try_stmt
|
|
||||||
.finalizer
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |f| self.body_contains_step(&f.stmts));
|
|
||||||
|
|
||||||
if has_steps {
|
|
||||||
self.errors.push(validation::error_step_in_catch(
|
|
||||||
self.span_line(try_stmt.span),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_return(&mut self, ret: &ReturnStmt) -> Option<(String, String)> {
|
|
||||||
let line = self.span_line(ret.span);
|
|
||||||
let id = self.next_id();
|
|
||||||
let node_id = self.add_node(DagNode {
|
|
||||||
id: id.clone(),
|
|
||||||
node_type: DagNodeType::Return,
|
|
||||||
label: "return".to_string(),
|
|
||||||
line,
|
|
||||||
});
|
|
||||||
Some((node_id.clone(), node_id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract workflow function params (no longer skips ctx)
|
|
||||||
fn extract_ts_params(params: &[swc_ecma_ast::Param], cm: &Lrc<SourceMap>) -> Vec<Param> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
for param in params {
|
|
||||||
let (name, typ) = match ¶m.pat {
|
|
||||||
Pat::Ident(BindingIdent { id, type_ann, .. }) => {
|
|
||||||
let name = id.sym.to_string();
|
|
||||||
let typ = type_ann.as_ref().map(|ann| {
|
|
||||||
cm.span_to_snippet(ann.type_ann.span())
|
|
||||||
.unwrap_or_else(|_| "unknown".to_string())
|
|
||||||
});
|
|
||||||
(name, typ)
|
|
||||||
}
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
result.push(Param { name, typ });
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_ts_workflow(code: &str) -> Result<WorkflowDag, Vec<CompileError>> {
|
|
||||||
let cm: Lrc<SourceMap> = Default::default();
|
|
||||||
let fm = cm.new_source_file(FileName::Custom("workflow.ts".into()).into(), code.into());
|
|
||||||
let lexer = Lexer::new(
|
|
||||||
Syntax::Typescript(TsSyntax::default()),
|
|
||||||
Default::default(),
|
|
||||||
StringInput::from(&*fm),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut parser = Parser::new_from(lexer);
|
|
||||||
let module = parser
|
|
||||||
.parse_module()
|
|
||||||
.map_err(|e| vec![CompileError { message: format!("Parse error: {e:?}"), line: 0 }])?;
|
|
||||||
|
|
||||||
// First pass: collect task functions
|
|
||||||
let task_functions = collect_task_functions(&module);
|
|
||||||
|
|
||||||
// Find: export default workflow(async (...) => { ... })
|
|
||||||
// or: export default workflow(async function(...) { ... })
|
|
||||||
let mut workflow_body: Option<(&[Stmt], Vec<Param>)> = None;
|
|
||||||
|
|
||||||
for item in &module.body {
|
|
||||||
// export default workflow(async (...) => { ... })
|
|
||||||
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) = item {
|
|
||||||
if let Some(result) = find_workflow_call(&export.expr, &cm) {
|
|
||||||
workflow_body = Some(result);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// const wf = workflow(async (...) => { ... }); export default wf;
|
|
||||||
if let ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) = item {
|
|
||||||
if let DefaultDecl::Fn(_) = &export.decl {
|
|
||||||
// `export default async function(...) { ... }` — not wrapped in workflow(), skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) = item {
|
|
||||||
for decl in &var_decl.decls {
|
|
||||||
if let Some(init) = &decl.init {
|
|
||||||
if let Some(result) = find_workflow_call(init, &cm) {
|
|
||||||
workflow_body = Some(result);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (stmts, params) = workflow_body.ok_or_else(|| {
|
|
||||||
vec![CompileError {
|
|
||||||
message: "No workflow() wrapped async function found.".to_string(),
|
|
||||||
line: 0,
|
|
||||||
}]
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let source_hash = compute_source_hash(code);
|
|
||||||
|
|
||||||
let mut walker = TsWacWalker::new(cm, task_functions);
|
|
||||||
walker.walk_body(stmts);
|
|
||||||
|
|
||||||
if !walker.errors.is_empty() {
|
|
||||||
return Err(walker.errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(WorkflowDag { nodes: walker.nodes, edges: walker.edges, params, source_hash })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find workflow(async (...) => { ... }) or workflow(async function(...) { ... })
|
|
||||||
fn find_workflow_call<'a>(expr: &'a Expr, cm: &Lrc<SourceMap>) -> Option<(&'a [Stmt], Vec<Param>)> {
|
|
||||||
if let Expr::Call(call) = expr {
|
|
||||||
// Check if callee is `workflow`
|
|
||||||
let is_workflow = match &call.callee {
|
|
||||||
Callee::Expr(callee_expr) => {
|
|
||||||
if let Expr::Ident(ident) = callee_expr.as_ref() {
|
|
||||||
ident.sym.as_ref() == "workflow"
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_workflow {
|
|
||||||
if let Some(first_arg) = call.args.first() {
|
|
||||||
return extract_async_fn_body(&first_arg.expr, cm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_async_fn_body<'a>(
|
|
||||||
expr: &'a Expr,
|
|
||||||
cm: &Lrc<SourceMap>,
|
|
||||||
) -> Option<(&'a [Stmt], Vec<Param>)> {
|
|
||||||
match expr {
|
|
||||||
Expr::Arrow(arrow) if arrow.is_async => {
|
|
||||||
let params = extract_arrow_params(&arrow.params, cm);
|
|
||||||
match &*arrow.body {
|
|
||||||
BlockStmtOrExpr::BlockStmt(block) => Some((&block.stmts, params)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::Fn(fn_expr) if fn_expr.function.is_async => {
|
|
||||||
let params = extract_ts_params(&fn_expr.function.params, cm);
|
|
||||||
fn_expr
|
|
||||||
.function
|
|
||||||
.body
|
|
||||||
.as_ref()
|
|
||||||
.map(|body| (body.stmts.as_slice(), params))
|
|
||||||
}
|
|
||||||
Expr::Paren(p) => extract_async_fn_body(&p.expr, cm),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract arrow function params (no longer skips ctx)
|
|
||||||
fn extract_arrow_params(pats: &[Pat], cm: &Lrc<SourceMap>) -> Vec<Param> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
for pat in pats {
|
|
||||||
match pat {
|
|
||||||
Pat::Ident(BindingIdent { id, type_ann, .. }) => {
|
|
||||||
let name = id.sym.to_string();
|
|
||||||
let typ = type_ann.as_ref().map(|ann| {
|
|
||||||
cm.span_to_snippet(ann.type_ann.span())
|
|
||||||
.unwrap_or_else(|_| "unknown".to_string())
|
|
||||||
});
|
|
||||||
result.push(Param { name, typ });
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_string_lit(expr: &Expr) -> Option<String> {
|
|
||||||
match expr {
|
|
||||||
Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()),
|
|
||||||
Expr::Tpl(tpl) if tpl.exprs.is_empty() && tpl.quasis.len() == 1 => {
|
|
||||||
tpl.quasis.first().map(|q| q.raw.to_string())
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_source_hash(code: &str) -> String {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(code.as_bytes());
|
|
||||||
format!("{:x}", hasher.finalize())
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct CompileError {
|
|
||||||
pub message: String,
|
|
||||||
pub line: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for CompileError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "line {}: {}", self.line, self.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_step_in_try(line: usize) -> CompileError {
|
|
||||||
CompileError {
|
|
||||||
message:
|
|
||||||
"Task calls inside try/except are not allowed. Steps have built-in error handling."
|
|
||||||
.to_string(),
|
|
||||||
line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_step_in_while(line: usize) -> CompileError {
|
|
||||||
CompileError {
|
|
||||||
message: "Task calls inside while loops are not allowed. Use for loops instead."
|
|
||||||
.to_string(),
|
|
||||||
line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_step_in_nested_function(line: usize) -> CompileError {
|
|
||||||
CompileError {
|
|
||||||
message: "Task calls inside nested functions, closures, or lambdas are not allowed."
|
|
||||||
.to_string(),
|
|
||||||
line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_step_in_comprehension(line: usize) -> CompileError {
|
|
||||||
CompileError { message: "Task calls inside comprehensions are not allowed.".to_string(), line }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_not_async(line: usize) -> CompileError {
|
|
||||||
CompileError { message: "Workflow function must be async.".to_string(), line }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_missing_await(line: usize) -> CompileError {
|
|
||||||
CompileError {
|
|
||||||
message:
|
|
||||||
"Task calls must be awaited directly or used inside asyncio.gather()/Promise.all()."
|
|
||||||
.to_string(),
|
|
||||||
line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_step_in_catch(line: usize) -> CompileError {
|
|
||||||
CompileError {
|
|
||||||
message:
|
|
||||||
"Task calls inside catch blocks are not allowed. Steps have built-in error handling."
|
|
||||||
.to_string(),
|
|
||||||
line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
use windmill_parser_wac::dag::DagNodeType;
|
|
||||||
use windmill_parser_wac::python::parse_python_workflow;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simple_sequential_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task
|
|
||||||
async def extract_data(url: str): ...
|
|
||||||
@task
|
|
||||||
async def load_data(data: list): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_etl(url: str):
|
|
||||||
raw = await extract_data(url=url)
|
|
||||||
await load_data(data=raw)
|
|
||||||
return {"status": "done"}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_python_workflow(code).expect("should parse");
|
|
||||||
assert_eq!(dag.nodes.len(), 3); // 2 steps + 1 return
|
|
||||||
assert_eq!(dag.edges.len(), 2); // step0->step1, step1->return
|
|
||||||
|
|
||||||
// Check params (url — no ctx to skip)
|
|
||||||
assert_eq!(dag.params.len(), 1);
|
|
||||||
assert_eq!(dag.params[0].name, "url");
|
|
||||||
assert_eq!(dag.params[0].typ.as_deref(), Some("str"));
|
|
||||||
|
|
||||||
// Check first step
|
|
||||||
match &dag.nodes[0].node_type {
|
|
||||||
DagNodeType::Step { name, script } => {
|
|
||||||
assert_eq!(name, "extract_data");
|
|
||||||
assert_eq!(script, "extract_data");
|
|
||||||
}
|
|
||||||
_ => panic!("expected Step node"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check second step
|
|
||||||
match &dag.nodes[1].node_type {
|
|
||||||
DagNodeType::Step { name, script } => {
|
|
||||||
assert_eq!(name, "load_data");
|
|
||||||
assert_eq!(script, "load_data");
|
|
||||||
}
|
|
||||||
_ => panic!("expected Step node"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check return
|
|
||||||
assert!(matches!(dag.nodes[2].node_type, DagNodeType::Return));
|
|
||||||
|
|
||||||
// Check source hash is non-empty
|
|
||||||
assert!(!dag.source_hash.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parallel_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task
|
|
||||||
async def extract_data(url: str): ...
|
|
||||||
@task
|
|
||||||
async def clean_data(data: list): ...
|
|
||||||
@task
|
|
||||||
async def compute_stats(data: list): ...
|
|
||||||
@task
|
|
||||||
async def load_to_warehouse(rows: list): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_etl(url: str):
|
|
||||||
raw = await extract_data(url=url)
|
|
||||||
cleaned, stats = await asyncio.gather(
|
|
||||||
clean_data(data=raw),
|
|
||||||
compute_stats(data=raw),
|
|
||||||
)
|
|
||||||
await load_to_warehouse(rows=cleaned)
|
|
||||||
return {"status": "done"}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_python_workflow(code).expect("should parse");
|
|
||||||
|
|
||||||
// extract, ParallelStart, clean, stats, ParallelEnd, load, return = 7
|
|
||||||
assert_eq!(dag.nodes.len(), 7);
|
|
||||||
|
|
||||||
assert!(matches!(dag.nodes[0].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[1].node_type, DagNodeType::ParallelStart));
|
|
||||||
assert!(matches!(dag.nodes[2].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[3].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[4].node_type, DagNodeType::ParallelEnd));
|
|
||||||
assert!(matches!(dag.nodes[5].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[6].node_type, DagNodeType::Return));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_conditional_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task
|
|
||||||
async def send_alert(msg: str): ...
|
|
||||||
@task
|
|
||||||
async def load_data(): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_etl(count: int):
|
|
||||||
if count > 100:
|
|
||||||
await send_alert(msg="large")
|
|
||||||
await load_data()
|
|
||||||
return {"done": True}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_python_workflow(code).expect("should parse");
|
|
||||||
// Branch, notify step, load step, return = 4
|
|
||||||
assert_eq!(dag.nodes.len(), 4);
|
|
||||||
assert!(matches!(dag.nodes[0].node_type, DagNodeType::Branch { .. }));
|
|
||||||
assert!(matches!(dag.nodes[1].node_type, DagNodeType::Step { .. }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_for_loop_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task
|
|
||||||
async def process_item(item: str): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_etl(items: list):
|
|
||||||
for item in items:
|
|
||||||
await process_item(item=item)
|
|
||||||
return {"done": True}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_python_workflow(code).expect("should parse");
|
|
||||||
// LoopStart, step, LoopEnd, return = 4
|
|
||||||
assert_eq!(dag.nodes.len(), 4);
|
|
||||||
assert!(matches!(
|
|
||||||
dag.nodes[0].node_type,
|
|
||||||
DagNodeType::LoopStart { .. }
|
|
||||||
));
|
|
||||||
assert!(matches!(dag.nodes[1].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[2].node_type, DagNodeType::LoopEnd));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reject_step_in_try() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task
|
|
||||||
async def extract_data(): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_etl():
|
|
||||||
try:
|
|
||||||
await extract_data()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_python_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("try/except"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reject_step_in_while() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task
|
|
||||||
async def extract_data(): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_etl():
|
|
||||||
while True:
|
|
||||||
await extract_data()
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_python_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("while"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reject_non_async() {
|
|
||||||
let code = r#"
|
|
||||||
from wmill import workflow
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
def my_etl():
|
|
||||||
pass
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_python_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("async"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reject_missing_await() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task
|
|
||||||
async def extract_data(): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_etl():
|
|
||||||
extract_data()
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_python_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("awaited"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_no_workflow_function() {
|
|
||||||
let code = r#"
|
|
||||||
async def my_func():
|
|
||||||
pass
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_python_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("No @workflow"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_task_with_external_path() {
|
|
||||||
let code = r#"
|
|
||||||
import asyncio
|
|
||||||
from wmill import workflow, task
|
|
||||||
|
|
||||||
@task(path="f/external_script")
|
|
||||||
async def run_external(x: int): ...
|
|
||||||
|
|
||||||
@workflow
|
|
||||||
async def my_wf(x: int):
|
|
||||||
result = await run_external(x=x)
|
|
||||||
return result
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_python_workflow(code).expect("should parse");
|
|
||||||
assert_eq!(dag.nodes.len(), 2); // 1 step + 1 return (bare `return` is not a step node but walk_return creates one)
|
|
||||||
match &dag.nodes[0].node_type {
|
|
||||||
DagNodeType::Step { name, script } => {
|
|
||||||
assert_eq!(name, "run_external");
|
|
||||||
assert_eq!(script, "f/external_script");
|
|
||||||
}
|
|
||||||
_ => panic!("expected Step node"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
use windmill_parser_wac::dag::DagNodeType;
|
|
||||||
use windmill_parser_wac::typescript::parse_ts_workflow;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simple_sequential_ts_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const extract_data = task(async (url: string) => {});
|
|
||||||
const load_data = task(async (data: any) => {});
|
|
||||||
|
|
||||||
export default workflow(async (url: string) => {
|
|
||||||
const raw = await extract_data(url);
|
|
||||||
await load_data(raw);
|
|
||||||
return { status: "done" };
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_ts_workflow(code).expect("should parse");
|
|
||||||
assert_eq!(dag.nodes.len(), 3); // 2 steps + 1 return
|
|
||||||
assert_eq!(dag.edges.len(), 2);
|
|
||||||
|
|
||||||
// Check params (url — no ctx to skip)
|
|
||||||
assert_eq!(dag.params.len(), 1);
|
|
||||||
assert_eq!(dag.params[0].name, "url");
|
|
||||||
assert_eq!(dag.params[0].typ.as_deref(), Some("string"));
|
|
||||||
|
|
||||||
match &dag.nodes[0].node_type {
|
|
||||||
DagNodeType::Step { name, script } => {
|
|
||||||
assert_eq!(name, "extract_data");
|
|
||||||
assert_eq!(script, "extract_data");
|
|
||||||
}
|
|
||||||
_ => panic!("expected Step node"),
|
|
||||||
}
|
|
||||||
|
|
||||||
match &dag.nodes[1].node_type {
|
|
||||||
DagNodeType::Step { name, script } => {
|
|
||||||
assert_eq!(name, "load_data");
|
|
||||||
assert_eq!(script, "load_data");
|
|
||||||
}
|
|
||||||
_ => panic!("expected Step node"),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(matches!(dag.nodes[2].node_type, DagNodeType::Return));
|
|
||||||
assert!(!dag.source_hash.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parallel_ts_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const extract_data = task(async (url: string) => {});
|
|
||||||
const clean_data = task(async (data: any) => {});
|
|
||||||
const compute_stats = task(async (data: any) => {});
|
|
||||||
const load_to_warehouse = task(async (rows: any) => {});
|
|
||||||
|
|
||||||
export default workflow(async (url: string) => {
|
|
||||||
const raw = await extract_data(url);
|
|
||||||
|
|
||||||
const [cleaned, stats] = await Promise.all([
|
|
||||||
clean_data(raw),
|
|
||||||
compute_stats(raw),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await load_to_warehouse(cleaned);
|
|
||||||
return { status: "done", rows: stats.rowCount };
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_ts_workflow(code).expect("should parse");
|
|
||||||
// extract, ParallelStart, clean, stats, ParallelEnd, load, return = 7
|
|
||||||
assert_eq!(dag.nodes.len(), 7);
|
|
||||||
|
|
||||||
assert!(matches!(dag.nodes[0].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[1].node_type, DagNodeType::ParallelStart));
|
|
||||||
assert!(matches!(dag.nodes[2].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[3].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[4].node_type, DagNodeType::ParallelEnd));
|
|
||||||
assert!(matches!(dag.nodes[5].node_type, DagNodeType::Step { .. }));
|
|
||||||
assert!(matches!(dag.nodes[6].node_type, DagNodeType::Return));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_conditional_ts_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const send_alert = task(async (msg: string) => {});
|
|
||||||
const load_data = task(async () => {});
|
|
||||||
|
|
||||||
export default workflow(async (count: number) => {
|
|
||||||
if (count > 100) {
|
|
||||||
await send_alert("large");
|
|
||||||
}
|
|
||||||
await load_data();
|
|
||||||
return { done: true };
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_ts_workflow(code).expect("should parse");
|
|
||||||
// Branch, notify, load, return = 4
|
|
||||||
assert_eq!(dag.nodes.len(), 4);
|
|
||||||
assert!(matches!(dag.nodes[0].node_type, DagNodeType::Branch { .. }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_for_of_ts_workflow() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const process_item = task(async (item: string) => {});
|
|
||||||
|
|
||||||
export default workflow(async (items: string[]) => {
|
|
||||||
for (const item of items) {
|
|
||||||
await process_item(item);
|
|
||||||
}
|
|
||||||
return { done: true };
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_ts_workflow(code).expect("should parse");
|
|
||||||
// LoopStart, step, LoopEnd, return = 4
|
|
||||||
assert_eq!(dag.nodes.len(), 4);
|
|
||||||
assert!(matches!(
|
|
||||||
dag.nodes[0].node_type,
|
|
||||||
DagNodeType::LoopStart { .. }
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reject_step_in_try_catch() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const extract_data = task(async () => {});
|
|
||||||
|
|
||||||
export default workflow(async () => {
|
|
||||||
try {
|
|
||||||
await extract_data();
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_ts_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("catch"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reject_step_in_while_ts() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const extract_data = task(async () => {});
|
|
||||||
|
|
||||||
export default workflow(async () => {
|
|
||||||
while (true) {
|
|
||||||
await extract_data();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_ts_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("while"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reject_missing_await_ts() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const extract_data = task(async () => {});
|
|
||||||
|
|
||||||
export default workflow(async () => {
|
|
||||||
extract_data();
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_ts_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("awaited"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_no_workflow_wrapper() {
|
|
||||||
let code = r#"
|
|
||||||
export default async function main(ctx: any) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = parse_ts_workflow(code);
|
|
||||||
assert!(result.is_err());
|
|
||||||
let errors = result.unwrap_err();
|
|
||||||
assert!(errors[0].message.contains("No workflow()"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_variable_declaration_with_step() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const compute = task(async () => {});
|
|
||||||
|
|
||||||
export default workflow(async () => {
|
|
||||||
const result = await compute();
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_ts_workflow(code).expect("should parse");
|
|
||||||
assert_eq!(dag.nodes.len(), 2); // step + return
|
|
||||||
assert!(matches!(dag.nodes[0].node_type, DagNodeType::Step { .. }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_task_with_external_path() {
|
|
||||||
let code = r#"
|
|
||||||
import { workflow, task } from "windmill-client";
|
|
||||||
|
|
||||||
const run_external = task("f/external_script", async (x: number) => {});
|
|
||||||
|
|
||||||
export default workflow(async (x: number) => {
|
|
||||||
const result = await run_external(x);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let dag = parse_ts_workflow(code).expect("should parse");
|
|
||||||
assert_eq!(dag.nodes.len(), 2); // step + return
|
|
||||||
match &dag.nodes[0].node_type {
|
|
||||||
DagNodeType::Step { name, script } => {
|
|
||||||
assert_eq!(name, "run_external");
|
|
||||||
assert_eq!(script, "f/external_script");
|
|
||||||
}
|
|
||||||
_ => panic!("expected Step node"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,8 +38,6 @@ csharp-parser = [ "dep:windmill-parser-csharp"]
|
|||||||
nu-parser = [ "dep:windmill-parser-nu"]
|
nu-parser = [ "dep:windmill-parser-nu"]
|
||||||
java-parser = [ "dep:windmill-parser-java"]
|
java-parser = [ "dep:windmill-parser-java"]
|
||||||
ruby-parser = [ "dep:windmill-parser-ruby"]
|
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]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@@ -57,10 +55,6 @@ windmill-parser-csharp = { workspace = true, optional = true }
|
|||||||
windmill-parser-nu = { workspace = true, optional = true }
|
windmill-parser-nu = { workspace = true, optional = true }
|
||||||
windmill-parser-java = { workspace = true, optional = true }
|
windmill-parser-java = { workspace = true, optional = true }
|
||||||
windmill-parser-ruby = { 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
|
wasm-bindgen.workspace = true
|
||||||
|
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|||||||
@@ -56,17 +56,6 @@ const targets = [
|
|||||||
features: "ruby-parser",
|
features: "ruby-parser",
|
||||||
env: "tree-sitter",
|
env: "tree-sitter",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
ident: "wac",
|
|
||||||
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 ^^^
|
# ^^^ Add new entry here ^^^
|
||||||
];
|
];
|
||||||
# NOTE: This is legacy command for building all, but it is not more used
|
# NOTE: This is legacy command for building all, but it is not more used
|
||||||
|
|||||||
@@ -33,6 +33,3 @@ popd
|
|||||||
|
|
||||||
pushd "pkg-java" && npm publish ${args}
|
pushd "pkg-java" && npm publish ${args}
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd "pkg-asset" && npm publish ${args}
|
|
||||||
popd
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user