Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions[bot] (aider)
6eb5ca5ba9 chore: Increase flow status update warning timeout 2025-05-20 12:47:43 +00:00
github-actions[bot] (aider)
c3fa90f245 chore: Increase db operation warn timeouts 2025-05-20 12:45:55 +00:00
969 changed files with 32562 additions and 63410 deletions

View File

@@ -47,7 +47,6 @@ Windmill uses a workspace-based architecture with multiple crates:
- Group related routes together
- Use consistent response formats (JSON)
- Follow proper authentication and authorization patterns
- Do not forget to update backend/windmill-api/openapi.yaml after modifying an api endpoint
## Performance Optimizations

View File

@@ -5,42 +5,12 @@ on:
types: [submitted]
jobs:
check-membership:
auto-fix-review:
if: github.event.review.state == 'changes_requested' && contains(github.event.pull_request.title, '[Aider PR]')
runs-on: ubicloud-standard-2
outputs:
is_member: ${{ steps.check-membership.outputs.is_member }}
steps:
- name: Check organization membership
id: check-membership
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
REVIEWER: ${{ github.event.review.user.login }}
ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }}
run: |
ORG="windmill-labs"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $ORG_ACCESS_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/orgs/$ORG/members/$REVIEWER")
if [ "$STATUS" -eq 204 ]; then
echo "is_member=true" >> $GITHUB_OUTPUT
else
echo "is_member=false" >> $GITHUB_OUTPUT
fi
check-and-prepare:
needs: check-membership
if: github.event.review.state == 'changes_requested' && contains(github.event.pull_request.title, '[Aider PR]') && needs.check-membership.outputs.is_member == 'true'
runs-on: ubicloud-standard-2
runs-on: ubicloud-standard-8
permissions:
contents: write
pull-requests: write
outputs:
prompt_content: ${{ steps.prepare_prompt.outputs.prompt_content }}
env:
GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
@@ -49,46 +19,200 @@ jobs:
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
steps:
- name: Acknowledge Request
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git User
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Checkout PR Branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
echo "Commenting on PR #${{ github.event.pull_request.number }} to acknowledge the /aider command."
gh pr comment ${{ github.event.pull_request.number }} --body "🤖 Aider is starting to work on your request. Please be patient, this might take a few minutes." --repo $GITHUB_REPOSITORY
echo "PR review trigger: Checking out PR branch..."
PR_NUMBER=${{ github.event.pull_request.number }}
PR_HEAD_REF=$(gh pr view $PR_NUMBER --json headRefName -q .headRefName --repo $GITHUB_REPOSITORY)
if [[ -z "$PR_HEAD_REF" || "$PR_HEAD_REF" == "null" ]]; then
echo "::error::Could not determine PR head branch for PR #$PR_NUMBER via gh CLI."
exit 1
fi
echo "Checking out PR head branch: $PR_HEAD_REF for PR #$PR_NUMBER"
git fetch origin "refs/heads/${PR_HEAD_REF}:refs/remotes/origin/${PR_HEAD_REF}" --no-tags
git checkout "$PR_HEAD_REF"
echo "Successfully checked out branch $(git rev-parse --abbrev-ref HEAD)"
- name: Prepare prompt for Aider
id: prepare_prompt
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Aider and Dependencies
run: |
python -m pip install aider-install; aider-install
pip install -U google-generativeai
sudo apt-get update && sudo apt-get install -y jq
- name: Generate Prompt from Review
id: generate_prompt
shell: bash
env:
GITHUB_REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REVIEW_BODY: ${{ github.event.review.body }}
run: |
REVIEW_BODY_ESCAPED="${REVIEW_BODY//\\/\\\\}"
REVIEW_BODY_ESCAPED="${REVIEW_BODY_ESCAPED//\"/\\\"}"
mkdir -p .github/aider
PROMPT_FILE_PATH=".github/aider/review-prompt.txt"
# Get PR review body
REVIEW_BODY="${{ github.event.review.body }}"
REVIEW_BODY_Q=$(printf '%q' "$REVIEW_BODY")
PR_NUMBER="${{ github.event.pull_request.number }}"
# Get PR description for context NOT USED FOR NOW
# PR_DETAILS=$(gh pr view $PR_NUMBER --json title,body --repo $GITHUB_REPOSITORY)
# PR_TITLE=$(echo "$PR_DETAILS" | jq -r .title)
# PR_BODY=$(echo "$PR_DETAILS" | jq -r .body)
# Get all PR review comments
ALL_REVIEW_COMMENTS=$(gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments)
FORMATTED_COMMENTS=$(jq -r '[.[] | {diff_hunk: .diff_hunk, path: .path, body: .body}]' <<< "$ALL_REVIEW_COMMENTS")
/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments \
| jq '[.[] | {diff_hunk: .diff_hunk, path: .path, body: .body}]')
BASE_PROMPT="Fix the following issues in the PR based on the review feedback. The review body is prepended with REVIEW. The review comments are prepended with REVIEW_COMMENTS. The review body and comments are separated by a blank line."
COMPLETE_PROMPT=$(printf "%s\nREVIEW:\n%s\nREVIEW_COMMENTS:\n%s" \
"$BASE_PROMPT" "$REVIEW_BODY_Q" "$ALL_REVIEW_COMMENTS")
echo "$COMPLETE_PROMPT" > "$PROMPT_FILE_PATH"
echo "PROMPT_FILE_PATH=$PROMPT_FILE_PATH" >> $GITHUB_OUTPUT
COMPLETE_PROMPT="${BASE_PROMPT}"$'\n'"REVIEW:"$'\n'"${REVIEW_BODY_ESCAPED}"$'\n'"REVIEW_COMMENTS:"$'\n'"${FORMATTED_COMMENTS}"
- name: Probe Chat for Relevant Files
id: probe_files
env:
PROMPT_CONTENT_FILE: ${{ steps.generate_prompt.outputs.PROMPT_FILE_PATH }}
run: |
echo "Running probe-chat to find relevant files..."
if [[ ! -f "$PROMPT_CONTENT_FILE" ]]; then
echo "::error::Prompt file $PROMPT_CONTENT_FILE not found!"
exit 1
fi
PROMPT_CONTENT=$(cat "$PROMPT_CONTENT_FILE")
if [ -z "$PROMPT_CONTENT" ]; then
echo "::error::Prompt content is empty!"
exit 1
fi
echo "prompt_content<<EOF" >> $GITHUB_OUTPUT
echo "$COMPLETE_PROMPT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
PROMPT_ESCAPED=$(jq -Rs . <<< "$PROMPT_CONTENT")
run-aider:
needs: [check-membership, check-and-prepare]
if: github.event.review.state == 'changes_requested' && contains(github.event.pull_request.title, '[Aider PR]') && needs.check-membership.outputs.is_member == 'true'
uses: ./.github/workflows/aider-common.yml
with:
needs_processing: false
base_prompt: ${{ needs.check-and-prepare.outputs.prompt_content }}
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
secrets: inherit
MESSAGE_FOR_PROBE=$(jq -n --arg prompt_escaped "$PROMPT_ESCAPED" \
'{ "message": "I'\''m giving you a request that needs to be implemented. Your role is ONLY to give me the files that are relevant to the request and nothing else. The request is prepended with the word REQUEST.\\nREQUEST: \($prompt_escaped). Give me all the files relevant to this request. Your output MUST be a single json array that can be parsed with programatic json parsing, with the relevant files. Files can be rust or typescript or javascript files. DO NOT INCLUDE ANY OTHER TEXT IN YOUR OUTPUT. ONLY THE JSON ARRAY. Example of output: [\"file1.py\", \"file2.py\"]" }' | jq -r .message)
set -o pipefail
PROBE_OUTPUT=$(npx --yes @buger/probe-chat@latest --max-iterations 50 --model-name gemini-2.5-pro-preview-05-06 --message "$MESSAGE_FOR_PROBE") || {
echo "::error::probe-chat command failed. Output:"
echo "$PROBE_OUTPUT"
exit 1
}
set +o pipefail
echo "Probe-chat raw output:"
echo "$PROBE_OUTPUT"
JSON_FILES=$(echo "$PROBE_OUTPUT" | sed -n '/^\s*\[/,$p' | sed '/^\s*\]/q')
echo "Extracted JSON block:"
echo "$JSON_FILES"
FILES_LIST=$(echo "$JSON_FILES" | jq -e -r '[.[] | select(type == "string" and . != "" and . != null and (endswith("/") | not))] | map(@sh) | join(" ")' || echo "")
if [[ -z "$FILES_LIST" ]]; then
echo "::warning::probe-chat did not identify any relevant files."
exit 1
fi
echo "Formatted files list for aider: $FILES_LIST"
echo "FILES_TO_EDIT=$FILES_LIST" >> $GITHUB_ENV
- name: Run Aider with review prompt
run: |
aider \
--read .cursor/rules/rust-best-practices.mdc \
--read .cursor/rules/svelte5-best-practices.mdc \
--read .cursor/rules/windmill-overview.mdc \
${{ env.FILES_TO_EDIT }} \
--model gemini/gemini-2.5-pro-preview-05-06 \
--message-file .github/aider/review-prompt.txt \
--yes \
--no-check-update \
--auto-commits \
--no-analytics \
--no-gitignore \
| tee .github/aider/aider-output.txt || true
echo "Aider command completed. Output saved to .github/aider/aider-output.txt"
# Check if there are any changes to commit
if [[ -z "$(git status --porcelain)" ]]; then
echo "No changes detected after running Aider."
echo "HAS_CHANGES=false" >> $GITHUB_OUTPUT
exit 0
fi
- name: Clean up prompt file
if: always()
run: rm -f .github/aider/review-prompt.txt
- name: Commit and Push Changes
id: commit_and_push
if: ${{ success() }}
run: |
CURRENT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
echo "Attempting to push changes to PR branch $CURRENT_BRANCH_NAME for PR #${{ github.event.pull_request.number }}"
# Pull latest changes to avoid rejection due to non-fast-forward
git config pull.rebase true
git pull origin $CURRENT_BRANCH_NAME
if git push origin $CURRENT_BRANCH_NAME; then
echo "Push to $CURRENT_BRANCH_NAME successful."
echo "CHANGES_APPLIED=true" >> $GITHUB_OUTPUT
else
echo "::warning::Push to PR branch $CURRENT_BRANCH_NAME failed."
echo "CHANGES_APPLIED=false" >> $GITHUB_OUTPUT
fi
- name: Comment on PR
if: success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUM: ${{ github.event.pull_request.number }}
run: |
# Create comment body in a temporary file to avoid command line length limits
if [[ "${{ steps.commit_and_push.outputs.CHANGES_APPLIED }}" == "true" ]]; then
cat > /tmp/pr-comment.md << EOL
🤖 I've automatically addressed the feedback based on the review.
## Aider Output
\`\`\`
$(cat .github/aider/aider-output.txt || echo 'No output available')
\`\`\`
Please review the changes and let me know if further adjustments are needed.
EOL
else
cat > /tmp/pr-comment.md << EOL
🤖 I attempted to address the review feedback, but no modifications were made.
## Aider Output
\`\`\`
$(cat .github/aider/aider-output.txt || echo 'No output available')
\`\`\`
Please review the output and provide additional guidance if needed.
EOL
fi
# Use the file for comment body
gh pr comment $PR_NUM --body-file /tmp/pr-comment.md

View File

@@ -1,522 +0,0 @@
name: Aider Common Steps
on:
workflow_call:
inputs:
issue_title:
description: "Title of the issue or PR"
required: false
type: string
issue_body:
description: "Body of the issue or PR"
required: false
type: string
instruction:
description: "Instruction for Aider"
required: false
type: string
issue_id:
description: "ID of the issue or PR"
required: false
type: string
needs_processing:
description: "Whether the issue needs to be processed by the external API"
required: false
type: boolean
default: true
base_prompt:
description: "Base prompt for Aider"
required: false
type: string
default: "Try to fix the following issue based on the instruction given by the user. The issue is prepended with the word ISSUE. The instruction is prepended with the word INSTRUCTION. The issue and instruction are separated by a blank line."
probe_prompt:
description: "Prompt for probe-chat"
required: false
type: string
default: 'I''m giving you a request that needs to be implemented. Your role is ONLY to give me the files that are relevant to the request and nothing else. The request is prepended with the word REQUEST. Give me all the files relevant to this request. Your output MUST be a single json array that can be parsed with programatic json parsing, with the relevant files. Files can be rust or typescript or javascript files. DO NOT INCLUDE ANY OTHER TEXT IN YOUR OUTPUT. ONLY THE JSON ARRAY. Example of output: ["file1.py", "file2.py"]'
rules_files:
description: "Rules files for Aider"
required: false
type: string
outputs:
files_to_edit:
description: "Files identified by probe-chat for editing"
value: ${{ jobs.common-steps.outputs.files_to_edit }}
final_prompt:
description: "Final prompt for Aider"
value: ${{ jobs.common-steps.outputs.final_prompt }}
pr_branch_name:
description: "Name of the branch used for PR"
value: ${{ jobs.common-steps.outputs.pr_branch_name }}
changes_applied_message:
description: "Message indicating changes were applied"
value: ${{ jobs.common-steps.outputs.changes_applied_message }}
changes_applied:
description: "Boolean indicating if changes were successfully applied"
value: ${{ jobs.common-steps.outputs.changes_applied }}
jobs:
common-steps:
runs-on: ubicloud-standard-8
outputs:
files_to_edit: ${{ steps.probe_files.outputs.files_to_edit }}
final_prompt: ${{ steps.create_prompt.outputs.final_prompt }}
pr_branch_name: ${{ steps.commit_and_push.outputs.PR_BRANCH_NAME }}
changes_applied_message: ${{ steps.commit_and_push.outputs.CHANGES_APPLIED_MESSAGE }}
changes_applied: ${{ steps.commit_and_push.outputs.CHANGES_APPLIED }}
env:
GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Checkout PR Branch
id: checkout_pr
if: (github.event_name == 'issue_comment' && github.event.issue.pull_request) || (github.event_name == 'pull_request_review')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Issue comment trigger: Checking out PR branch..."
PR_NUMBER=""
if [ -n "${{ github.event.issue.number }}" ]; then
PR_NUMBER="${{ github.event.issue.number }}"
elif [ -n "${{ github.event.pull_request.number }}" ]; then
PR_NUMBER="${{ github.event.pull_request.number }}"
else
echo "::error::Could not determine PR number."
exit 1
fi
PR_HEAD_REF=$(gh pr view $PR_NUMBER --json headRefName -q .headRefName --repo $GITHUB_REPOSITORY)
if [[ -z "$PR_HEAD_REF" || "$PR_HEAD_REF" == "null" ]]; then
echo "::error::Could not determine PR head branch for PR #$PR_NUMBER via gh CLI."
exit 1
fi
echo "Checking out PR head branch: $PR_HEAD_REF for PR #$PR_NUMBER"
git fetch origin "refs/heads/${PR_HEAD_REF}:refs/remotes/origin/${PR_HEAD_REF}" --no-tags
git checkout "$PR_HEAD_REF"
echo "Successfully checked out branch $(git rev-parse --abbrev-ref HEAD)"
echo "PR_BRANCH=$PR_HEAD_REF" >> $GITHUB_OUTPUT
- name: Configure Git User
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Cache Python dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install Aider and Dependencies
run: |
echo "Installing Aider..."
python -m pip install uv
python -m venv ~/uv-env
source ~/uv-env/bin/activate
uv pip install configargparse==1.7
uv pip install aider-chat==0.83.1
uv pip install -U google-generativeai
sudo apt-get update && sudo apt-get install -y jq
echo "$HOME/.local/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV_PATH=$HOME/uv-env" >> $GITHUB_ENV
- name: Create Prompt for Aider
id: create_prompt
shell: bash
env:
BASE_PROMPT_ENV: ${{ inputs.base_prompt }}
ISSUE_TITLE_ENV: ${{ inputs.issue_title }}
ISSUE_BODY_ENV: ${{ inputs.issue_body }}
INSTRUCTION_ENV: ${{ inputs.instruction }}
NEEDS_PROCESSING_ENV: ${{ inputs.needs_processing }}
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
run: |
set -e
FINAL_PROMPT_CONTENT=""
if [[ "$ISSUE_TITLE_ENV" != "" && "$ISSUE_BODY_ENV" != "" ]]; then
echo "Processing issue with title: $ISSUE_TITLE_ENV"
if [[ "$NEEDS_PROCESSING_ENV" == "true" ]]; then
echo "Needs processing is true. Calling Windmill API..."
JSON_PAYLOAD=$(jq -n \
--arg title "$ISSUE_TITLE_ENV" \
--arg body "$ISSUE_BODY_ENV" \
'{"body":{"issue_title":$title,"issue_body":$body}}')
echo "Windmill JSON Payload: $JSON_PAYLOAD"
API_RESULT_FILE=$(mktemp)
HTTP_CODE=$(curl -s -o "$API_RESULT_FILE" -w "%{http_code}" \
-X POST "https://app.windmill.dev/api/w/windmill-labs/jobs/run_wait_result/p/f/ai/quiet_script" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WINDMILL_TOKEN" \
--data-binary "$JSON_PAYLOAD" \
--max-time 90)
BODY_CONTENT=$(cat "$API_RESULT_FILE")
rm -f "$API_RESULT_FILE" # Clean up temp file
echo "Windmill API HTTP Code: $HTTP_CODE"
if [[ "$HTTP_CODE" -eq 200 ]]; then
PROCESSED_ISSUE_PROMPT=$(echo "$BODY_CONTENT" | jq -r '.effective_body // empty')
if [[ -z "$PROCESSED_ISSUE_PROMPT" || "$PROCESSED_ISSUE_PROMPT" == "null" ]]; then
echo "::warning::Windmill API returned 200 but effective_body was empty or null."
EFFECTIVE_ISSUE_CONTENT_FOR_PROMPT="$ISSUE_BODY_ENV"
else
echo "Successfully processed issue via Windmill API."
EFFECTIVE_ISSUE_CONTENT_FOR_PROMPT="$PROCESSED_ISSUE_PROMPT"
fi
FINAL_PROMPT_CONTENT=$(printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT_ENV" "$EFFECTIVE_ISSUE_CONTENT_FOR_PROMPT" "$INSTRUCTION_ENV")
else
echo "::error::Windmill API call failed (HTTP $HTTP_CODE). Using raw issue content for prompt."
FINAL_PROMPT_CONTENT=$(printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT_ENV" "$ISSUE_BODY_ENV" "$INSTRUCTION_ENV")
fi
else
echo "Needs processing is false. Using raw issue content for prompt."
FINAL_PROMPT_CONTENT=$(printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT_ENV" "$ISSUE_BODY_ENV" "$INSTRUCTION_ENV")
fi
else
echo "No issue title or body given. Using base prompt."
FINAL_PROMPT_CONTENT=$(printf "%s\nINSTRUCTION:\n%s" "$BASE_PROMPT_ENV" "$INSTRUCTION_ENV")
fi
echo "Final prompt: $FINAL_PROMPT_CONTENT"
echo "final_prompt<<EOF_AIDER_PROMPT" >> "$GITHUB_OUTPUT"
echo "$FINAL_PROMPT_CONTENT" >> "$GITHUB_OUTPUT"
echo "EOF_AIDER_PROMPT" >> "$GITHUB_OUTPUT"
- name: Probe Chat for Relevant Files
id: probe_files
shell: bash
env:
FINAL_PROMPT: ${{ steps.create_prompt.outputs.final_prompt }}
PROBE_PROMPT: ${{ inputs.probe_prompt }}
run: |
echo "Running probe-chat to find relevant files..."
MESSAGE_FOR_PROBE=$(printf "%s\nREQUEST:\n%s" "$PROBE_PROMPT" "$FINAL_PROMPT")
set -o pipefail
PROBE_OUTPUT=$(npx --yes @buger/probe-chat@latest --max-iterations 50 --model-name gemini-2.5-pro-preview-05-06 --message "$MESSAGE_FOR_PROBE") || {
echo "::error::probe-chat command failed. Output:"
echo "$PROBE_OUTPUT"
exit 1
}
set +o pipefail
echo "Probe-chat raw output:"
echo "$PROBE_OUTPUT"
JSON_FILES=$(echo "$PROBE_OUTPUT" | sed -n '/^\s*\[/,$p' | sed '/^\s*\]/q')
echo "Extracted JSON block:"
echo "$JSON_FILES"
FILES_LIST=$(echo "$JSON_FILES" | jq -e -r '[.[] | select(type == "string" and . != "" and . != null and (endswith("/") | not))] | join(" ")' || echo "")
if [[ -z "$FILES_LIST" ]]; then
echo "::warning::probe-chat did not identify any relevant files."
fi
echo "Formatted files list for aider: $FILES_LIST"
echo "files_to_edit=$FILES_LIST" >> $GITHUB_OUTPUT
- name: Cache Aider tags
uses: actions/cache@v3
with:
path: .aider.tags.cache.v4
key: ${{ runner.os }}-aider-${{ github.sha }}
restore-keys: |
${{ runner.os }}-aider-
- name: Prepare branch for Aider
id: prepare_branch
env:
ISSUE_ID: ${{ inputs.issue_id }}
run: |
if [[ "$ISSUE_ID" != "" ]]; then
BRANCH_NAME="aider-fix-issue-${ISSUE_ID}"
# Check if branch exists remotely
if git ls-remote --heads origin $BRANCH_NAME | grep -q $BRANCH_NAME; then
echo "Branch $BRANCH_NAME already exists remotely, fetching it"
git fetch origin $BRANCH_NAME
git checkout $BRANCH_NAME
git pull origin $BRANCH_NAME
else
echo "Creating new branch $BRANCH_NAME"
git checkout -b $BRANCH_NAME
fi
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
else
# We're in a pull_request_review event
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_HEAD_REF="${{ github.event.pull_request.head.ref }}"
echo "Handling pull_request_review for PR #$PR_NUMBER on branch $PR_HEAD_REF"
# Ensure we're on the correct branch
git config pull.rebase true
git fetch origin $PR_HEAD_REF
git checkout $PR_HEAD_REF
git pull origin $PR_HEAD_REF
echo "Using PR branch $PR_HEAD_REF for PR #$PR_NUMBER"
echo "BRANCH_NAME=$PR_HEAD_REF" >> $GITHUB_OUTPUT
fi
- name: Run Aider
id: run_aider
shell: bash
env:
FILES_TO_EDIT: ${{ steps.probe_files.outputs.files_to_edit }}
FINAL_PROMPT: ${{ steps.create_prompt.outputs.final_prompt }}
RULES_FILES: ${{ inputs.rules_files }}
run: |
source $VIRTUAL_ENV_PATH/bin/activate
echo "$FINAL_PROMPT" > .aider_final_prompt.txt
echo "FILES_TO_EDIT: $FILES_TO_EDIT"
RULES=""
if [ -n "$RULES_FILES" ]; then
for rule in $RULES_FILES; do
RULES="$RULES --read $rule"
done
fi
aider \
$RULES \
$FILES_TO_EDIT \
--model gemini/gemini-2.5-pro-preview-05-06 \
--message-file .aider_final_prompt.txt \
--yes \
--no-check-update \
--auto-commits \
--no-analytics \
--no-gitignore \
| tee .aider_output.txt || true
echo "Aider command completed. Output saved to .aider_output.txt"
- name: Cache Node.js dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-
- name: Commit and Push Changes
id: commit_and_push
env:
ISSUE_ID: ${{ inputs.issue_id }}
BRANCH_NAME: ${{ steps.prepare_branch.outputs.BRANCH_NAME }}
run: |
if [[ "$ISSUE_ID" != "" ]]; then
# Check if there are any uncommitted changes
if [[ -n $(git status --porcelain) ]]; then
echo "Found uncommitted changes, committing them"
git add .
git commit -m "Aider changes"
fi
# Push changes to the branch
if git push origin $BRANCH_NAME; then
echo "Pushed to branch $BRANCH_NAME"
echo "PR_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "CHANGES_APPLIED_MESSAGE=Aider changes pushed to branch $BRANCH_NAME." >> $GITHUB_OUTPUT
echo "CHANGES_APPLIED=true" >> $GITHUB_OUTPUT
else
echo "::warning::Push to PR branch $BRANCH_NAME failed."
echo "CHANGES_APPLIED_MESSAGE=Aider ran, but failed to push changes to PR branch $BRANCH_NAME." >> $GITHUB_OUTPUT
echo "CHANGES_APPLIED=false" >> $GITHUB_OUTPUT
fi
else
# We're in a pull_request_review event
PR_HEAD_REF="${{ github.event.pull_request.head.ref }}"
echo "Attempting to push changes to PR branch $PR_HEAD_REF"
if git push origin $PR_HEAD_REF; then
echo "Push to $PR_HEAD_REF successful (or no new changes to push)."
echo "CHANGES_APPLIED_MESSAGE=Aider changes (if any) pushed to PR branch $PR_HEAD_REF." >> $GITHUB_OUTPUT
echo "PR_BRANCH_NAME=$PR_HEAD_REF" >> $GITHUB_OUTPUT
echo "CHANGES_APPLIED=true" >> $GITHUB_OUTPUT
else
echo "::warning::Push to PR branch $PR_HEAD_REF failed."
echo "CHANGES_APPLIED_MESSAGE=Aider ran, but failed to push changes to PR branch $PR_HEAD_REF." >> $GITHUB_OUTPUT
echo "CHANGES_APPLIED=false" >> $GITHUB_OUTPUT
fi
fi
- name: Create Pull Request
if: always() && (github.event_name == 'issue_comment' || github.event_name == 'repository_dispatch') && !github.event.issue.pull_request && steps.commit_and_push.outputs.PR_BRANCH_NAME != ''
id: create_pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_BRANCH: ${{ steps.commit_and_push.outputs.PR_BRANCH_NAME }}
ISSUE_NUM: ${{ inputs.issue_id }}
ISSUE_TITLE: ${{ inputs.issue_title }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
run: |
# Create PR description in a temporary file to avoid command line length limits and ensure it stays under 40k chars
HEADER="This PR was created automatically by Aider to fix issue #${ISSUE_NUM}."
# if event is repository_dispatch, add the issue title to the header
if [ "$GITHUB_EVENT_NAME" == "repository_dispatch" ]; then
if [[ "${{ github.event.client_payload.source }}" == "linear" ]]; then
HEADER="This PR was created automatically by Aider to fix issue #linear:${ISSUE_NUM}."
elif [[ "${{ github.event.client_payload.source }}" == "discord" ]]; then
HEADER="This PR was created automatically by Aider to fix issue #discord:${ISSUE_NUM}."
fi
fi
cat > /tmp/pr-description.md << EOL | head -c 40000
$HEADER
## Aider Output
\`\`\`
$(cat .aider_output.txt || echo "No output available")
\`\`\`
EOL
# Create PR using the file for the body content, handle errors gracefully
set +e # Don't exit on error
PR_TITLE="[Aider PR] Fix: ${ISSUE_TITLE}"
if [ -z "$ISSUE_TITLE" ]; then
PR_TITLE="[Aider PR] AI changes after request"
fi
gh pr create \
--title "$PR_TITLE" \
--body-file /tmp/pr-description.md \
--head "$PR_BRANCH" \
--base main \
--draft
PR_CREATE_EXIT_CODE=$?
set -e # Re-enable exit on error
if [ $PR_CREATE_EXIT_CODE -eq 0 ]; then
echo "PR created successfully"
PR_URL=$(gh pr view $PR_BRANCH --json url --jq .url)
echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT
echo "PR_CREATED=true" >> $GITHUB_OUTPUT
else
echo "Warning: Failed to create PR. Exit code: $PR_CREATE_EXIT_CODE"
echo "PR_CREATED=false" >> $GITHUB_OUTPUT
# Continue workflow despite PR creation failure
fi
- name: Comment on PR with Aider Output
if: always() && github.event_name == 'pull_request_review' && steps.commit_and_push.outputs.CHANGES_APPLIED != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUM: ${{ github.event.pull_request.number }}
JOB_STATUS: ${{ job.status }}
run: |
# Create comment body in a temporary file to avoid command line length limits
if [[ "${{ steps.commit_and_push.outputs.CHANGES_APPLIED }}" == "true" ]]; then
if [[ "$JOB_STATUS" == "success" ]]; then
STATUS_PREFIX="🤖 I've automatically addressed the feedback based on the review."
else
STATUS_PREFIX="⚠️ I attempted to address the feedback, but encountered some issues."
fi
else
if [[ "$JOB_STATUS" == "success" ]]; then
STATUS_PREFIX="🤖 I attempted to address the review feedback, but no modifications were made."
else
STATUS_PREFIX="⚠️ I encountered issues while attempting to address the feedback, and no modifications were made."
fi
fi
cat > /tmp/pr-comment.md << EOL
${STATUS_PREFIX}
## Aider Output
\`\`\`
$(cat .aider_output.txt || echo 'No output available')
\`\`\`
Please review the output and provide additional guidance if needed.
EOL
# Use the file for comment body
gh pr comment $PR_NUM --body-file /tmp/pr-comment.md
- name: Comment on issue/PR to let the user know Aider has finished working on the request
if: always() && github.event_name == 'issue_comment'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
JOB_STATUS: ${{ job.status }}
PR_CREATED: ${{ steps.create_pr.outputs.PR_CREATED }}
PR_URL: ${{ steps.create_pr.outputs.PR_URL }}
run: |
echo "Commenting on issue/PR #${{ github.event.issue.number }} to let the user know Aider has finished working on the request."
if [[ "$JOB_STATUS" == "success" ]]; then
if [[ "$PR_CREATED" == "true" ]]; then
COMMENT_BODY="🤖 Aider has finished working on your request. A PR has been created. $PR_URL"
else
COMMENT_BODY="🤖 Aider has finished working on your request, but was unable to create a PR."
fi
else
COMMENT_BODY="⚠️ Aider encountered issues while working on your request. Please check the workflow logs for details."
fi
gh issue comment ${{ github.event.issue.number }} --body "$COMMENT_BODY" --repo $GITHUB_REPOSITORY
- name: Comment on linear issue to let the user know Aider has finished working on the request
if: always() && github.event_name == 'repository_dispatch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
JOB_STATUS: ${{ job.status }}
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
PR_CREATED: ${{ steps.create_pr.outputs.PR_CREATED }}
PR_URL: ${{ steps.create_pr.outputs.PR_URL }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
SOURCE: ${{ github.event.client_payload.source }}
run: |
echo "Notifying user about Aider completion status for $SOURCE request #${{ github.event.client_payload.issue_id }}"
if [[ "$JOB_STATUS" == "success" ]]; then
if [[ "$PR_CREATED" == "true" ]]; then
COMMENT_BODY="🤖 Aider has finished working on your request. A PR has been created. $PR_URL"
else
COMMENT_BODY="🤖 Aider has finished working on your request, but was unable to create a PR."
fi
else
COMMENT_BODY="⚠️ Aider encountered issues while working on your request. Please check the workflow logs for details."
fi
if [[ "$SOURCE" == "discord" ]]; then
curl -X POST \
-H "Authorization: Bot $DISCORD_BOT_TOKEN" \
-H "Content-Type: application/json" \
"https://discord.com/api/v10/channels/${{ github.event.client_payload.channel_id }}/messages" \
-d "{\"content\":\"${COMMENT_BODY}\"}"
else
curl -X POST \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
"https://api.linear.app/graphql" \
-d "{\"query\":\"mutation { commentCreate(input: { issueId: \\\"${{ github.event.client_payload.issue_id }}\\\", body: \\\"${COMMENT_BODY}\\\" }) { success } }\"}"
fi

View File

@@ -1,80 +0,0 @@
name: External Aider Issue Fix
on:
repository_dispatch:
types: [external_issue_fix]
jobs:
check-and-prepare:
runs-on: ubicloud-standard-2
permissions:
contents: write
pull-requests: write
outputs:
issue_title: ${{ steps.determine_inputs.outputs.ISSUE_TITLE }}
issue_body: ${{ steps.determine_inputs.outputs.ISSUE_BODY }}
instruction: ${{ steps.determine_inputs.outputs.INSTRUCTION }}
env:
GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
steps:
- name: Acknowledge Request
env:
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
run: |
if [[ "${{ github.event.client_payload.source }}" == "linear" ]]; then
echo "Commenting on Linear issue #${{ github.event.client_payload.issue_id }} to acknowledge the request."
curl -X POST \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
"https://api.linear.app/graphql" \
-d "{\"query\":\"mutation { commentCreate(input: { issueId: \\\"${{ github.event.client_payload.issue_id }}\\\", body: \\\"🤖 Aider is starting to work on your request. I'll update you here once I have a PR ready. Please be patient, this might take a few minutes.\\\" }) { success } }\"}"
elif [[ "${{ github.event.client_payload.source }}" == "discord" ]]; then
echo "Commenting on Discord thread #${{ github.event.client_payload.channel_id }} to acknowledge the request."
curl -X POST \
-H "Authorization: Bot $DISCORD_BOT_TOKEN" \
-H "Content-Type: application/json" \
"https://discord.com/api/v10/channels/${{ github.event.client_payload.channel_id }}/messages" \
-d "{\"content\":\"🤖 Aider is starting to work on your request. I'll update you here once I have a PR ready. Please be patient, this might take a few minutes.\"}"
fi
- name: Determine inputs for Aider
id: determine_inputs
shell: bash
env:
ISSUE_TITLE: ${{ github.event.client_payload.issue_title }}
ISSUE_BODY: ${{ github.event.client_payload.issue_body }}
INSTRUCTION: ${{ github.event.client_payload.instruction }}
run: |
echo "Determining inputs for Aider..."
echo "ISSUE_TITLE<<EOF_AIDER_TITLE" >> "$GITHUB_OUTPUT"
echo "$ISSUE_TITLE" >> "$GITHUB_OUTPUT"
echo "EOF_AIDER_TITLE" >> "$GITHUB_OUTPUT"
echo "ISSUE_BODY<<EOF_AIDER_BODY" >> "$GITHUB_OUTPUT"
echo "$ISSUE_BODY" >> "$GITHUB_OUTPUT"
echo "EOF_AIDER_BODY" >> "$GITHUB_OUTPUT"
echo "INSTRUCTION<<EOF_AIDER_INSTRUCTION" >> "$GITHUB_OUTPUT"
echo "$INSTRUCTION" >> "$GITHUB_OUTPUT"
echo "EOF_AIDER_INSTRUCTION" >> "$GITHUB_OUTPUT"
echo "Finished determining inputs."
run-aider:
needs: check-and-prepare
uses: ./.github/workflows/aider-common.yml
with:
issue_title: ${{ needs.check-and-prepare.outputs.issue_title }}
issue_body: ${{ needs.check-and-prepare.outputs.issue_body }}
instruction: ${{ needs.check-and-prepare.outputs.instruction }}
issue_id: ${{ github.event.client_payload.issue_id }}
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
secrets: inherit

View File

@@ -5,40 +5,12 @@ on:
types: [created]
jobs:
check-membership:
runs-on: ubicloud-standard-2
auto-fix:
runs-on: ubicloud-standard-8
if: |
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '/aider') &&
!contains(github.event.comment.user.login, '[bot]')
outputs:
is_member: ${{ steps.check-membership.outputs.is_member }}
steps:
- name: Check organization membership
id: check-membership
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
COMMENTER: ${{ github.event.comment.user.login }}
ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }}
run: |
ORG="windmill-labs"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $ORG_ACCESS_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/orgs/$ORG/members/$COMMENTER")
if [ "$STATUS" -eq 204 ]; then
echo "is_member=true" >> $GITHUB_OUTPUT
else
echo "is_member=false" >> $GITHUB_OUTPUT
fi
check-and-prepare:
needs: check-membership
runs-on: ubicloud-standard-2
if: needs.check-membership.outputs.is_member == 'true'
permissions:
contents: write
pull-requests: write
@@ -49,117 +21,323 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
outputs:
issue_title: ${{ steps.determine_inputs.outputs.ISSUE_TITLE }}
issue_body: ${{ steps.determine_inputs.outputs.ISSUE_BODY }}
comment_content: ${{ steps.determine_inputs.outputs.COMMENT_CONTENT }}
pr_branch: ${{ steps.checkout_pr.outputs.PR_BRANCH }}
steps:
- name: Acknowledge Request
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git User
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Checkout PR Branch
if: github.event_name == 'issue_comment' && github.event.issue.pull_request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
echo "Commenting on issue/PR #${{ github.event.issue.number }} to acknowledge the /aider command."
gh issue comment ${{ github.event.issue.number }} --body "🤖 Aider is starting to work on your request. I'll update you here once I have a PR ready. Please be patient, this might take a few minutes." --repo $GITHUB_REPOSITORY
echo "Issue comment trigger: Checking out PR branch..."
PR_NUMBER=${{ github.event.issue.number }}
PR_HEAD_REF=$(gh pr view $PR_NUMBER --json headRefName -q .headRefName --repo $GITHUB_REPOSITORY)
if [[ -z "$PR_HEAD_REF" || "$PR_HEAD_REF" == "null" ]]; then
echo "::error::Could not determine PR head branch for PR #$PR_NUMBER via gh CLI."
exit 1
fi
echo "Checking out PR head branch: $PR_HEAD_REF for PR #$PR_NUMBER"
git fetch origin "refs/heads/${PR_HEAD_REF}:refs/remotes/origin/${PR_HEAD_REF}" --no-tags
git checkout "$PR_HEAD_REF"
echo "Successfully checked out branch $(git rev-parse --abbrev-ref HEAD)"
- name: Determine inputs for Aider
id: determine_inputs
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Aider and Dependencies
run: |
python -m pip install aider-install; aider-install
pip install -U google-generativeai
sudo apt-get update && sudo apt-get install -y jq
- name: Determine Prompt for Aider
id: determine_prompt
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_BODY: ${{ github.event.comment.body }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
GITHUB_REPOSITORY: ${{ github.repository }}
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
run: |
echo "Determining inputs for Aider..."
ISSUE_TITLE_VAL=""
ISSUE_BODY_VAL=""
PROMPT_FILE_PATH=".github/aider/issue-prompt.txt"
mkdir -p .github/aider
# Determine if this is a PR comment or regular issue comment
if [[ ! -z "${{ github.event.issue.pull_request }}" ]]; then
echo "This is a comment on a Pull Request"
PR_NUMBER="$ISSUE_NUMBER"
PR_NUMBER="${{ github.event.issue.number }}"
PR_BODY_JSON=$(gh pr view "$PR_NUMBER" --json body --repo "$GITHUB_REPOSITORY")
if [[ $? -ne 0 ]]; then
echo "Error fetching PR body for PR #$PR_NUMBER"
PR_BODY_VAL=""
else
PR_BODY_VAL=$(jq -r '.body // ""' <<< "$PR_BODY_JSON")
fi
# Get PR description to check for issue references
PR_BODY=$(gh pr view $PR_NUMBER --json body -q .body --repo $GITHUB_REPOSITORY)
if [[ ! -z "$PR_BODY_VAL" ]]; then
REFERENCED_ISSUE=""
if [[ "$PR_BODY_VAL" =~ \#linear:([a-f0-9-]+) ]]; then
REFERENCED_ISSUE="${BASH_REMATCH[1]}"
echo "Found referenced Linear issue #$REFERENCED_ISSUE in PR description"
LINEAR_ISSUE_JSON=$(curl -s -H "Authorization: $LINEAR_API_KEY" \
"https://api.linear.app/graphql" \
-X POST \
-H "Content-Type: application/json" \
-d "{\"query\":\"query { issue(id: \\\"$REFERENCED_ISSUE\\\") { title description } }\"}")
if [[ $? -eq 0 && ! "$LINEAR_ISSUE_JSON" =~ "error" ]]; then
ISSUE_TITLE_VAL=$(jq -r '.data.issue.title // ""' <<< "$LINEAR_ISSUE_JSON")
ISSUE_BODY_VAL=$(jq -r '.data.issue.description // ""' <<< "$LINEAR_ISSUE_JSON")
echo "Successfully fetched Linear issue details"
else
echo "Error fetching Linear issue details for #$REFERENCED_ISSUE"
fi
elif [[ "$PR_BODY_VAL" =~ \#([0-9]+) ]]; then
REFERENCED_ISSUE="${BASH_REMATCH[1]}"
echo "Found referenced GitHub issue #$REFERENCED_ISSUE in PR description"
ISSUE_DETAILS_JSON=$(gh issue view "$REFERENCED_ISSUE" --json title,body --repo "$GITHUB_REPOSITORY")
if [[ $? -ne 0 ]]; then
echo "Error fetching issue details for #$REFERENCED_ISSUE"
else
ISSUE_TITLE_VAL=$(jq -r '.title // ""' <<< "$ISSUE_DETAILS_JSON")
ISSUE_BODY_VAL=$(jq -r '.body // ""' <<< "$ISSUE_DETAILS_JSON")
# Extract issue number from PR description (looking for #123 or "fixes #123" patterns)
REFERENCED_ISSUE=$(echo "$PR_BODY" | grep -oE "#[0-9]+" | grep -oE "[0-9]+" | head -1)
if [[ ! -z "$REFERENCED_ISSUE" ]]; then
echo "Found referenced issue #$REFERENCED_ISSUE in PR description"
# Fetch the referenced issue details
ISSUE_DETAILS=$(gh issue view $REFERENCED_ISSUE --json title,body --repo $GITHUB_REPOSITORY)
ISSUE_TITLE=$(echo "$ISSUE_DETAILS" | jq -r .title)
ISSUE_BODY=$(echo "$ISSUE_DETAILS" | jq -r .body)
# Store raw comment body in a file first to avoid shell interpretation issues
echo '${{ github.event.comment.body }}' > /tmp/raw_comment.txt
RAW_COMMENT_BODY=$(cat /tmp/raw_comment.txt)
# Remove the /aider prefix and trim whitespace
COMMENT_CONTENT=$(echo "$RAW_COMMENT_BODY" | sed 's|^/aider||' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
echo "Sending issue content and PR comment to external API…"
ISSUE_TITLE_Q=$(printf '%q' "$ISSUE_TITLE")
ISSUE_BODY_Q=$(printf '%q' "$ISSUE_BODY")
JSON_PAYLOAD=$(jq -n \
--arg title "$ISSUE_TITLE_Q" \
--arg body "$ISSUE_BODY_Q" \
'{"body":{"issue_title":$title,"issue_body":$body}}')
API_RESULT=$(curl -s -w "\n%{http_code}" \
-X POST "https://app.windmill.dev/api/w/windmill-labs/jobs/run_wait_result/p/f/ai/quiet_script" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WINDMILL_TOKEN" \
--data-binary "$JSON_PAYLOAD" \
--max-time 90)
HTTP_CODE=$(echo "$API_RESULT" | tail -n1)
BODY=$(echo "$API_RESULT" | sed '$d')
echo "$BODY" > /tmp/api_response.txt
BASE_PROMPT="Try to fix the following issue based on the instruction given by the user. The issue is prepended with the word ISSUE. The instruction is prepended with the word INSTRUCTION. The issue and instruction are separated by a blank line."
if [[ "$HTTP_CODE" -eq 200 ]]; then
PROCESSED_ISSUE_PROMPT=$(jq -r '.effective_body // empty' /tmp/api_response.txt)
if [[ -z "$PROCESSED_ISSUE_PROMPT" || "$PROCESSED_ISSUE_PROMPT" == "null" ]]; then
PROCESSED_ISSUE_PROMPT=""
fi
printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT" "$PROCESSED_ISSUE_PROMPT" "$COMMENT_CONTENT" > "$PROMPT_FILE_PATH"
else
echo "::warning::API call failed (HTTP $HTTP_CODE). Using PR comment with issue context."
printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT" "$ISSUE_BODY_Q" "$COMMENT_CONTENT" > "$PROMPT_FILE_PATH"
fi
rm -f /tmp/api_response.txt
else
echo "PR body is empty or could not be fetched."
echo "No referenced issue found in PR description, using comment content only"
# Use comment content directly as with regular issue comments
echo '${{ github.event.comment.body }}' > /tmp/raw_comment.txt
RAW_COMMENT_BODY=$(cat /tmp/raw_comment.txt)
COMMENT_CONTENT=$(echo "$RAW_COMMENT_BODY" | sed 's|^/aider||' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
if [[ -z "$COMMENT_CONTENT" ]]; then
echo "::error::Comment with /aider provided, but no instruction found after it. Cannot proceed."
printf "Error: /aider command found but no instruction followed." > "$PROMPT_FILE_PATH"
exit 1
else
echo "Using comment content as prompt."
printf '%s' "$COMMENT_CONTENT" > "$PROMPT_FILE_PATH"
fi
fi
else
echo "This is a comment on a regular issue"
ISSUE_DETAILS_JSON=$(gh issue view "$ISSUE_NUMBER" --json title,body --repo "$GITHUB_REPOSITORY")
if [[ $? -ne 0 ]]; then
echo "Error fetching issue details for #$ISSUE_NUMBER"
# Fetch the issue details
ISSUE_NUMBER="${{ github.event.issue.number }}"
ISSUE_DETAILS=$(gh issue view $ISSUE_NUMBER --json title,body --repo $GITHUB_REPOSITORY)
ISSUE_TITLE=$(echo "$ISSUE_DETAILS" | jq -r .title)
ISSUE_BODY=$(echo "$ISSUE_DETAILS" | jq -r .body)
# Store raw comment body in a file first to avoid shell interpretation issues
echo '${{ github.event.comment.body }}' > /tmp/raw_comment.txt
# Extract the command part safely
RAW_COMMENT_BODY=$(cat /tmp/raw_comment.txt)
# Remove the /aider prefix and trim whitespace
COMMENT_CONTENT=$(echo "$RAW_COMMENT_BODY" | sed 's|^/aider||' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
if [[ -z "$COMMENT_CONTENT" ]]; then
echo "::error::Comment with /aider provided, but no instruction found after it. Cannot proceed."
printf "Error: /aider command found but no instruction followed." > "$PROMPT_FILE_PATH"
exit 1
else
ISSUE_TITLE_VAL=$(jq -r '.title // ""' <<< "$ISSUE_DETAILS_JSON")
ISSUE_BODY_VAL=$(jq -r '.body // ""' <<< "$ISSUE_DETAILS_JSON")
echo "Sending issue content and issue comment to external API…"
ISSUE_TITLE_Q=$(printf '%q' "$ISSUE_TITLE")
ISSUE_BODY_Q=$(printf '%q' "$ISSUE_BODY")
COMMENT_CONTENT_Q=$(printf '%q' "$COMMENT_CONTENT")
JSON_PAYLOAD=$(jq -n \
--arg title "$ISSUE_TITLE_Q" \
--arg body "$ISSUE_BODY_Q" \
--arg comment "$COMMENT_CONTENT_Q" \
'{"body":{"issue_title":$title,"issue_body":$body,"issue_comment":$comment}}')
API_RESULT=$(curl -s -w "\n%{http_code}" \
-X POST "https://app.windmill.dev/api/w/windmill-labs/jobs/run_wait_result/p/f/ai/quiet_script" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WINDMILL_TOKEN" \
--data-binary "$JSON_PAYLOAD" \
--max-time 90)
HTTP_CODE=$(echo "$API_RESULT" | tail -n1)
BODY=$(echo "$API_RESULT" | sed '$d')
echo "$BODY" > /tmp/api_response.txt
BASE_PROMPT="Try to fix the following issue based on the instruction given by the user. The issue is prepended with the word ISSUE. The instruction is prepended with the word INSTRUCTION. The issue and instruction are separated by a blank line."
if [[ "$HTTP_CODE" -eq 200 ]]; then
PROCESSED_ISSUE_PROMPT=$(jq -r '.effective_body // empty' /tmp/api_response.txt)
if [[ -z "$PROCESSED_ISSUE_PROMPT" || "$PROCESSED_ISSUE_PROMPT" == "null" ]]; then
PROCESSED_ISSUE_PROMPT=""
fi
printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT" "$PROCESSED_ISSUE_PROMPT" "$COMMENT_CONTENT" > "$PROMPT_FILE_PATH"
else
echo "::warning::API call failed (HTTP $HTTP_CODE). Using PR comment with issue context."
printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT" "$ISSUE_BODY_Q" "$COMMENT_CONTENT" > "$PROMPT_FILE_PATH"
fi
rm -f /tmp/api_response.txt
fi
fi
echo "Prompt determined and written to $PROMPT_FILE_PATH"
echo "PROMPT_FILE_PATH=$PROMPT_FILE_PATH" >> $GITHUB_OUTPUT
- name: Probe Chat for Relevant Files
id: probe_files
env:
PROMPT_CONTENT_FILE: ${{ steps.determine_prompt.outputs.PROMPT_FILE_PATH }}
run: |
echo "Running probe-chat to find relevant files..."
if [[ ! -f "$PROMPT_CONTENT_FILE" ]]; then
echo "::error::Prompt file $PROMPT_CONTENT_FILE not found!"
exit 1
fi
PROMPT_CONTENT=$(cat "$PROMPT_CONTENT_FILE")
if [ -z "$PROMPT_CONTENT" ]; then
echo "::error::Prompt content is empty!"
exit 1
fi
PROMPT_ESCAPED=$(jq -Rs . <<< "$PROMPT_CONTENT")
MESSAGE_FOR_PROBE=$(jq -n --arg prompt_escaped "$PROMPT_ESCAPED" \
'{ "message": "I'\''m giving you a request that needs to be implemented. Your role is ONLY to give me the files that are relevant to the request and nothing else. The request is prepended with the word REQUEST.\\nREQUEST: \($prompt_escaped). Give me all the files relevant to this request. Your output MUST be a single json array that can be parsed with programatic json parsing, with the relevant files. Files can be rust or typescript or javascript files. DO NOT INCLUDE ANY OTHER TEXT IN YOUR OUTPUT. ONLY THE JSON ARRAY. Example of output: [\"file1.py\", \"file2.py\"]" }' | jq -r .message)
set -o pipefail
PROBE_OUTPUT=$(npx --yes @buger/probe-chat@latest --max-iterations 50 --model-name gemini-2.5-pro-preview-05-06 --message "$MESSAGE_FOR_PROBE") || {
echo "::error::probe-chat command failed. Output:"
echo "$PROBE_OUTPUT"
exit 1
}
set +o pipefail
echo "Probe-chat raw output:"
echo "$PROBE_OUTPUT"
JSON_FILES=$(echo "$PROBE_OUTPUT" | sed -n '/^\s*\[/,$p' | sed '/^\s*\]/q')
echo "Extracted JSON block:"
echo "$JSON_FILES"
FILES_LIST=$(echo "$JSON_FILES" | jq -e -r '[.[] | select(type == "string" and . != "" and . != null and (endswith("/") | not))] | map(@sh) | join(" ")' || echo "")
if [[ -z "$FILES_LIST" ]]; then
echo "::warning::probe-chat did not identify any relevant files."
exit 1
fi
echo "Formatted files list for aider: $FILES_LIST"
echo "FILES_TO_EDIT=$FILES_LIST" >> $GITHUB_ENV
- name: Run Aider with external prompt
run: |
echo "Files identified by probe-chat: ${{ env.FILES_TO_EDIT }}"
aider \
--read .cursor/rules/rust-best-practices.mdc \
--read .cursor/rules/svelte5-best-practices.mdc \
--read .cursor/rules/windmill-overview.mdc \
${{ env.FILES_TO_EDIT }} \
--model gemini/gemini-2.5-pro-preview-05-06 \
--message-file .github/aider/issue-prompt.txt \
--yes \
--no-check-update \
--auto-commits \
--no-analytics \
--no-gitignore \
| tee .github/aider/aider-output.txt || true
echo "Aider command completed. Output saved to .github/aider/aider-output.txt"
- name: Clean up prompt file
if: always()
run: rm -f .github/aider/issue-prompt.txt
- name: Commit and Push Changes
id: commit_and_push
if: ${{ success() }}
run: |
if [[ -z "${{ github.event.issue.pull_request }}" ]]; then
BRANCH_NAME="aider-fix-issue-${{ github.event.issue.number }}"
# Check if branch exists remotely
if git ls-remote --heads origin $BRANCH_NAME | grep -q $BRANCH_NAME; then
echo "Branch $BRANCH_NAME already exists remotely, fetching it"
git fetch origin $BRANCH_NAME
git checkout $BRANCH_NAME
git pull origin $BRANCH_NAME
else
echo "Creating new branch $BRANCH_NAME"
git checkout -b $BRANCH_NAME
fi
echo "Created/checked out branch $BRANCH_NAME for issue #${{ github.event.issue.number }}"
git push origin $BRANCH_NAME
echo "Pushed to branch $BRANCH_NAME"
echo "PR_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "CHANGES_APPLIED_MESSAGE=Aider changes pushed to branch $BRANCH_NAME." >> $GITHUB_OUTPUT
else
CURRENT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
echo "Attempting to push changes to PR branch $CURRENT_BRANCH_NAME for PR #${{ github.event.issue.number }}"
if git push origin $CURRENT_BRANCH_NAME; then
echo "Push to $CURRENT_BRANCH_NAME successful (or no new changes to push)."
echo "CHANGES_APPLIED_MESSAGE=Aider changes (if any) pushed to PR branch $CURRENT_BRANCH_NAME." >> $GITHUB_OUTPUT
echo "PR_BRANCH_NAME=$CURRENT_BRANCH_NAME" >> $GITHUB_OUTPUT
else
echo "::warning::Push to PR branch $CURRENT_BRANCH_NAME failed."
echo "CHANGES_APPLIED_MESSAGE=Aider ran, but failed to push changes to PR branch $CURRENT_BRANCH_NAME." >> $GITHUB_OUTPUT
fi
fi
echo "ISSUE_TITLE<<EOF_AIDER_TITLE" >> "$GITHUB_OUTPUT"
echo "$ISSUE_TITLE_VAL" >> "$GITHUB_OUTPUT"
echo "EOF_AIDER_TITLE" >> "$GITHUB_OUTPUT"
- name: Create Pull Request
if: success() && github.event_name == 'issue_comment' && !github.event.issue.pull_request && steps.commit_and_push.outputs.PR_BRANCH_NAME != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_BRANCH: ${{ steps.commit_and_push.outputs.PR_BRANCH_NAME }}
ISSUE_NUM: ${{ github.event.issue.number }}
run: |
# Create PR description in a temporary file to avoid command line length limits
cat > /tmp/pr-description.md << EOL
This PR was created automatically by Aider to fix issue #${ISSUE_NUM}.
echo "ISSUE_BODY<<EOF_AIDER_BODY" >> "$GITHUB_OUTPUT"
echo "$ISSUE_BODY_VAL" >> "$GITHUB_OUTPUT"
echo "EOF_AIDER_BODY" >> "$GITHUB_OUTPUT"
## Aider Output
\`\`\`
$(cat .github/aider/aider-output.txt || echo "No output available")
\`\`\`
EOL
CLEAN_COMMENT="${COMMENT_BODY/\/aider/}"
CLEAN_COMMENT="${CLEAN_COMMENT#"${CLEAN_COMMENT%%[![:space:]]*}"}"
CLEAN_COMMENT="${CLEAN_COMMENT%"${CLEAN_COMMENT##*[![:space:]]}"}"
echo "COMMENT_CONTENT<<EOF_AIDER_COMMENT" >> "$GITHUB_OUTPUT"
echo "$CLEAN_COMMENT" >> "$GITHUB_OUTPUT"
echo "EOF_AIDER_COMMENT" >> "$GITHUB_OUTPUT"
echo "Finished determining inputs."
run-aider:
needs: [check-membership, check-and-prepare]
if: needs.check-membership.outputs.is_member == 'true'
uses: ./.github/workflows/aider-common.yml
with:
issue_title: ${{ needs.check-and-prepare.outputs.issue_title }}
issue_body: ${{ needs.check-and-prepare.outputs.issue_body }}
instruction: ${{ needs.check-and-prepare.outputs.comment_content }}
issue_id: ${{ github.event.issue.number }}
rules_files: ".cursor/rules/rust-best-practices.mdc .cursor/rules/svelte5-best-practices.mdc .cursor/rules/windmill-overview.mdc"
secrets: inherit
# Create PR using the file for the body content
gh pr create \
--title "[Aider PR] Add fixes for issue #${ISSUE_NUM}" \
--body-file /tmp/pr-description.md \
--head "$PR_BRANCH" \
--base main

View File

@@ -53,7 +53,7 @@ jobs:
timeout-minutes: 16
run: |
mkdir -p fake_frontend_build
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --features $(./all_features_oss.sh)
FRONTEND_BUILD_DIR=$(pwd)/fake_frontend_build SQLX_OFFLINE=true cargo check --all-features
check_ee:
runs-on: ubicloud-standard-8

View File

@@ -45,9 +45,9 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.1.43
- uses: astral-sh/setup-uv@v6
- uses: astral-sh/setup-uv@v4
with:
version: "0.6.2"
version: "0.4.18"
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: backend

View File

@@ -64,7 +64,7 @@ jobs:
platforms: linux/amd64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}
@@ -81,7 +81,7 @@ jobs:
platforms: linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp,private
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,license,otel,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
secrets: |
rh_username=${{ secrets.RH_USERNAME }}
rh_password=${{ secrets.RH_PASSWORD }}

View File

@@ -51,7 +51,7 @@ jobs:
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
mkdir frontend/build && cd backend
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages_windows,mcp,private
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,mcp
- name: Rename binary with corresponding architecture
run: |
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"

View File

@@ -1,4 +1,4 @@
name: Claude PR Assistant
name: Claude Code
on:
issue_comment:
@@ -11,52 +11,15 @@ on:
types: [submitted]
jobs:
check-membership:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai') && !contains(github.event.comment.user.login, '[bot]')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai') && !contains(github.event.comment.user.login, '[bot]')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai') && !contains(github.event.review.user.login, '[bot]')) ||
(github.event_name == 'issues' && contains(github.event.issue.body, '/ai') && !contains(github.event.issue.user.login, '[bot]'))
runs-on: ubicloud-standard-2
outputs:
is_member: ${{ steps.check-membership.outputs.is_member }}
steps:
- name: Check organization membership
id: check-membership
env:
ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }}
run: |
ORG="windmill-labs"
if [[ "${{ github.event_name }}" == "issue_comment" || "${{ github.event_name }}" == "pull_request_review_comment" ]]; then
COMMENTER="${{ github.event.comment.user.login }}"
elif [[ "${{ github.event_name }}" == "pull_request_review" ]]; then
COMMENTER="${{ github.event.review.user.login }}"
else
COMMENTER="${{ github.event.issue.user.login }}"
fi
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $ORG_ACCESS_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/orgs/$ORG/members/$COMMENTER")
if [ "$STATUS" -eq 204 ]; then
echo "is_member=true" >> $GITHUB_OUTPUT
else
echo "is_member=false" >> $GITHUB_OUTPUT
fi
claude-code-action:
needs: check-membership
if: |
needs.check-membership.outputs.is_member == 'true'
runs-on: ubicloud-standard-8
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
@@ -64,22 +27,9 @@ jobs:
with:
fetch-depth: 1
- name: Run Claude PR Action
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"
allowed_tools: "mcp__github__create_pull_request,Bash(npm run check),Bash(npm install),Bash(cargo check),Bash(curl https://sh.rustup.rs -sSf | sh -s -- -y)"
custom_instructions: "IMPORTANT INSTRUCTIONS:
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main, with the title starting with [Claude PR].
- If you made changes to the frontend code, run npm install, then npm run generate-backend-client, then npm run check. You can ignore warnings that are reported by the check script, but fix the errors.
- If you made changes to the backend code, install Rust and then run cargo check. You can ignore warnings that are reported by the check script, but fix the errors.
- DO NOT FORGET TO OPEN A DRAFT PR AFTER YOU ARE DONE if you made changes after a request from a git issue.
AVAILABLE TOOLS:
- mcp__github__create_pull_request: Create a PR from a branch to main
- Bash(npm run check): Run the check script. You should run this tool after making changes to the frontend code.
- Bash(npm install): Install dependencies. You need this to run npm run check.
- Bash(npm run generate-backend-client): Generate the backend client. You need this to run npm run check.
- Bash(cargo check): Run the cargo check script. You should run this tool after making changes to the backend code.
- Bash(curl https://sh.rustup.rs -sSf | sh -s -- -y): Install Rust. You need this to run cargo check."
trigger_phrase: "/ai"

View File

@@ -3,34 +3,8 @@ on:
types: [created]
jobs:
check-membership:
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') && github.event.comment.user.type != 'Bot' }}
runs-on: ubicloud-standard-2
outputs:
is_member: ${{ steps.check-membership.outputs.is_member }}
steps:
- name: Check organization membership
id: check-membership
env:
ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }}
COMMENTER: ${{ github.event.comment.user.login }}
run: |
ORG="windmill-labs"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $ORG_ACCESS_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/orgs/$ORG/members/$COMMENTER")
if [ "$STATUS" -eq 204 ]; then
echo "is_member=true" >> $GITHUB_OUTPUT
else
echo "is_member=false" >> $GITHUB_OUTPUT
fi
trigger-docs:
needs: check-membership
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') && needs.check-membership.outputs.is_member == 'true' }}
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/docs') }}
uses: windmill-labs/windmilldocs/.github/workflows/create-docs.yml@main
with:
pr_number: ${{ github.event.issue.number }}

View File

@@ -17,11 +17,8 @@ jobs:
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_STATUS: "opened"
PR_NUMBER: ${{ github.event.pull_request.number }}
DISCORD_CHANNEL_ID: "1372204995868491786"
DISCORD_GUILD_ID: "930051556043276338"
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_PR_REVIEWS_WEBHOOK }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
merge_success_emoji:
if: github.event.pull_request.merged == true
@@ -32,4 +29,4 @@ jobs:
DISCORD_GUILD_ID: "930051556043276338"
PR_NUMBER: ${{ github.event.pull_request.number }}
secrets:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_PR_BOT_TOKEN }}

View File

@@ -92,7 +92,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages,deno_core,mcp,private
features=embedding,parquet,openidconnect,jemalloc,license,http_trigger,zip,oauth2,dind,postgres_trigger,mqtt_trigger,websocket,smtp,static_frontend,agent_worker_server,all_languages,deno_core,mcp
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEV_SHA }}
${{ steps.meta-public.outputs.tags }}
@@ -154,7 +154,7 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
build-args: |
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,private,deno_core,mcp
features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,agent_worker_server,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,otel,dind,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,deno_core,mcp
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:${{ env.DEV_SHA }}
${{ steps.meta-ee-public.outputs.tags }}

View File

@@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: windmill-labs/windmill-helm-charts
token: ${{ secrets.HELM_CHART_TOKEN }}
token: ${{ secrets.DOCS_TOKEN }}
- name: Get version
id: get_version
@@ -57,7 +57,7 @@ jobs:
- name: Create PR
env:
GH_TOKEN: ${{ secrets.HELM_CHART_TOKEN }}
GH_TOKEN: ${{ secrets.DOCS_TOKEN }}
run: |
gh pr create \
--title "helm: bump version to ${{ env.VERSION }}" \

View File

@@ -1,38 +0,0 @@
name: Claude PR Assistant
on:
repository_dispatch:
types: [external_claude_issue_fix]
jobs:
claude-code-action:
runs-on: ubicloud-standard-8
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Process inputs
id: process_inputs
shell: bash
run: |
ISSUE_TITLE="${{ github.event.client_payload.issue_title }}"
INSTRUCTION="${{ github.event.client_payload.instruction }}"
ISSUE_BODY=$(printf '%q' "${{ github.event.client_payload.issue_body }}")
BASE_PROMPT="Try to fix the following issue based on the instruction given. You are provided with the issue title, issue body, and instruction. You are to fix the issue based on the instruction. You are to create a pull request to fix the issue."
CUSTOM_PROMPT=$(printf -v PROMPT "%s\n\nISSUE_TITLE: %s\n\nISSUE_BODY: %s\n\nINSTRUCTION: %s" "$BASE_PROMPT" "$ISSUE_TITLE" "$ISSUE_BODY" "$INSTRUCTION")
echo "CUSTOM_PROMPT=$CUSTOM_PROMPT" >> $GITHUB_OUTPUT
- name: Run Claude PR Action
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
timeout_minutes: "60"
allowed_tools: "mcp__github__create_pull_request"
direct_prompt: ${{ steps.process_inputs.outputs.CUSTOM_PROMPT }}

223
.github/workflows/linear-issue.yaml vendored Normal file
View File

@@ -0,0 +1,223 @@
name: External Aider Issue Fix
on:
repository_dispatch:
types: [external_issue_fix]
jobs:
auto-fix:
runs-on: ubicloud-standard-8
permissions:
contents: write
pull-requests: write
issues: write
env:
GEMINI_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WINDMILL_TOKEN: ${{ secrets.WINDMILL_TOKEN }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git User
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Aider and Dependencies
run: |
python -m pip install aider-install; aider-install
pip install -U google-generativeai
sudo apt-get update && sudo apt-get install -y jq
- name: Create Prompt for Aider
id: create_prompt
shell: bash
run: |
PROMPT_FILE_PATH=".github/aider/issue-prompt.txt"
mkdir -p .github/aider
ISSUE_TITLE="${{ github.event.client_payload.issue_title }}"
INSTRUCTION="${{ github.event.client_payload.instruction }}"
ISSUE_BODY=$(printf '%q' "${{ github.event.client_payload.issue_body }}")
echo "Processing issue with title: $ISSUE_TITLE"
JSON_PAYLOAD=$(jq -n \
--arg title "$ISSUE_TITLE" \
--arg body "$ISSUE_BODY" \
'{"body":{"issue_title":$title,"issue_body":$body}}')
API_RESULT=$(curl -s -w "\n%{http_code}" \
-X POST "https://app.windmill.dev/api/w/windmill-labs/jobs/run_wait_result/p/f/ai/quiet_script" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WINDMILL_TOKEN" \
--data-binary "$JSON_PAYLOAD" \
--max-time 90)
HTTP_CODE=$(echo "$API_RESULT" | tail -n1)
BODY=$(echo "$API_RESULT" | sed '$d')
echo "$BODY" > /tmp/api_response.txt
BASE_PROMPT="Try to fix the following issue based on the instruction given. The issue is prepended with the word ISSUE. The instruction is prepended with the word INSTRUCTION. The issue and instruction are separated by a blank line."
if [[ "$HTTP_CODE" -eq 200 ]]; then
PROCESSED_ISSUE_PROMPT=$(jq -r '.effective_body // empty' /tmp/api_response.txt)
if [[ -z "$PROCESSED_ISSUE_PROMPT" || "$PROCESSED_ISSUE_PROMPT" == "null" ]]; then
PROCESSED_ISSUE_PROMPT=""
fi
printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT" "$PROCESSED_ISSUE_PROMPT" "$INSTRUCTION" > "$PROMPT_FILE_PATH"
else
echo "::warning::API call failed (HTTP $HTTP_CODE). Using raw issue content."
printf "%s\nISSUE:\n%s\nINSTRUCTION:\n%s" \
"$BASE_PROMPT" "$ISSUE_BODY" "$INSTRUCTION" > "$PROMPT_FILE_PATH"
fi
rm -f /tmp/api_response.txt
echo "Prompt created and written to $PROMPT_FILE_PATH"
echo "PROMPT_FILE_PATH=$PROMPT_FILE_PATH" >> $GITHUB_OUTPUT
# Store the issue title for PR creation
ISSUE_TITLE_SAFE=$(echo "$ISSUE_TITLE" | tr -d '\n' | sed 's/"/\\"/g')
echo "ISSUE_TITLE=$ISSUE_TITLE_SAFE" >> $GITHUB_OUTPUT
# Generate unique branch name using timestamp and issue info
ISSUE_ID="${{ github.event.client_payload.issue_id }}"
BRANCH_NAME="aider-fix-linear-issue-$ISSUE_ID"
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Probe Chat for Relevant Files
id: probe_files
env:
PROMPT_CONTENT_FILE: ${{ steps.create_prompt.outputs.PROMPT_FILE_PATH }}
run: |
echo "Running probe-chat to find relevant files..."
if [[ ! -f "$PROMPT_CONTENT_FILE" ]]; then
echo "::error::Prompt file $PROMPT_CONTENT_FILE not found!"
exit 1
fi
PROMPT_CONTENT=$(cat "$PROMPT_CONTENT_FILE")
if [ -z "$PROMPT_CONTENT" ]; then
echo "::error::Prompt content is empty!"
exit 1
fi
PROMPT_ESCAPED=$(jq -Rs . <<< "$PROMPT_CONTENT")
MESSAGE_FOR_PROBE=$(jq -n --arg prompt_escaped "$PROMPT_ESCAPED" \
'{ "message": "I'\''m giving you a request that needs to be implemented. Your role is ONLY to give me the files that are relevant to the request and nothing else. The request is prepended with the word REQUEST.\\nREQUEST: \($prompt_escaped). Give me all the files relevant to this request. Your output MUST be a single json array that can be parsed with programatic json parsing, with the relevant files. Files can be rust or typescript or javascript files. DO NOT INCLUDE ANY OTHER TEXT IN YOUR OUTPUT. ONLY THE JSON ARRAY. Example of output: [\"file1.py\", \"file2.py\"]" }' | jq -r .message)
set -o pipefail
PROBE_OUTPUT=$(npx --yes @buger/probe-chat@latest --max-iterations 50 --model-name gemini-2.5-pro-preview-05-06 --message "$MESSAGE_FOR_PROBE") || {
echo "::error::probe-chat command failed. Output:"
echo "$PROBE_OUTPUT"
exit 1
}
set +o pipefail
echo "Probe-chat raw output:"
echo "$PROBE_OUTPUT"
JSON_FILES=$(echo "$PROBE_OUTPUT" | sed -n '/^\s*\[/,$p' | sed '/^\s*\]/q')
echo "Extracted JSON block:"
echo "$JSON_FILES"
FILES_LIST=$(echo "$JSON_FILES" | jq -e -r '[.[] | select(type == "string" and . != "" and . != null and (endswith("/") | not))] | map(@sh) | join(" ")' || echo "")
if [[ -z "$FILES_LIST" ]]; then
echo "::warning::probe-chat did not identify any relevant files."
exit 1
fi
echo "Formatted files list for aider: $FILES_LIST"
echo "FILES_TO_EDIT=$FILES_LIST" >> $GITHUB_ENV
- name: Run Aider with external prompt
run: |
echo "Files identified by probe-chat: ${{ env.FILES_TO_EDIT }}"
aider \
--read .cursor/rules/rust-best-practices.mdc \
--read .cursor/rules/svelte5-best-practices.mdc \
--read .cursor/rules/windmill-overview.mdc \
${{ env.FILES_TO_EDIT }} \
--model gemini/gemini-2.5-pro-preview-05-06 \
--message-file .github/aider/issue-prompt.txt \
--yes \
--no-check-update \
--auto-commits \
--no-analytics \
--no-gitignore \
| tee .github/aider/aider-output.txt || true
echo "Aider command completed. Output saved to .github/aider/aider-output.txt"
- name: Clean up prompt file
if: always()
run: rm -f .github/aider/issue-prompt.txt
- name: Commit and Push Changes
id: commit_and_push
if: ${{ success() }}
run: |
BRANCH_NAME="${{ steps.create_prompt.outputs.BRANCH_NAME }}"
# Check if branch exists remotely
if git ls-remote --heads origin $BRANCH_NAME | grep -q $BRANCH_NAME; then
echo "Branch $BRANCH_NAME already exists remotely, fetching it"
git fetch origin $BRANCH_NAME
git checkout $BRANCH_NAME
git pull origin $BRANCH_NAME
else
echo "Creating new branch $BRANCH_NAME"
git checkout -b $BRANCH_NAME
fi
# Check if there are any changes to commit
if git diff --quiet && git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -am "Auto-fix using Aider for external issue [skip ci]" || echo "No changes to commit"
fi
git push origin $BRANCH_NAME
echo "Pushed to branch $BRANCH_NAME"
echo "PR_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create Pull Request
if: success() && steps.commit_and_push.outputs.PR_BRANCH_NAME != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_BRANCH: ${{ steps.commit_and_push.outputs.PR_BRANCH_NAME }}
ISSUE_TITLE: ${{ steps.create_prompt.outputs.ISSUE_TITLE }}
ISSUE_ID: ${{ github.event.client_payload.issue_id }}
run: |
# Create PR description in a temporary file to avoid command line length limits
cat > /tmp/pr-description.md << EOL
This PR was created automatically by Aider to fix an external issue: ${ISSUE_TITLE}
## Aider Output
\`\`\`
$(cat .github/aider/aider-output.txt || echo "No output available")
\`\`\`
EOL
# Create PR using the file for the body content
gh pr create \
--title "[Aider PR] Fix: ${ISSUE_TITLE}" \
--body-file /tmp/pr-description.md \
--head "$PR_BRANCH" \
--base main || echo "PR already exists or couldn't be created"

View File

@@ -53,7 +53,7 @@ jobs:
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
mkdir frontend/build && cd backend
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages_windows,mcp,private
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,mcp
- name: Rename binary with corresponding architecture
run: |
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"

View File

@@ -1,19 +0,0 @@
name: Publish rust-client to crates.io on release
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
build_rust_and_publish_to_crates_io:
runs-on: ubicloud-standard-8
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v20
with:
extra_nix_config: |
experimental-features = nix-command flakes
- run: cd rust-client && nix develop ../ --command ./dev.nu --check --publish
env:
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}

View File

@@ -38,45 +38,24 @@ jobs:
- name: Send Discord notification and start a thread
env:
WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }}
GUILD_ID: ${{ inputs.DISCORD_GUILD_ID }}
PR_TITLE: ${{ inputs.PR_TITLE }}
PR_NUMBER: ${{ inputs.PR_NUMBER }}
PR_URL: ${{ inputs.PR_URL }}
PR_AUTHOR: ${{ inputs.PR_AUTHOR }}
run: |
# Check if thread already exists
thread_exists=false
if threads=$(curl -s -H "Authorization: Bot $BOT_TOKEN" "https://discord.com/api/v10/guilds/${GUILD_ID}/threads/active"); then
if thread_id=$(echo "$threads" | jq -r --arg cid "$CHANNEL_ID" --arg pref "#${PR_NUMBER}:" '.threads[] | select(.parent_id == $cid and (.name | startswith($pref))) | .id' 2>/dev/null); then
if [ -n "$thread_id" ]; then
thread_exists=true
echo "Thread already exists, skipping creation"
fi
fi
else
echo "Failed to check for existing threads, will create new thread"
fi
# Create thread if it doesn't exist or if check failed
if [ "$thread_exists" = false ]; then
echo "Creating new thread"
THREAD_TITLE="#${PR_NUMBER}: ${PR_TITLE} by \`${PR_AUTHOR}\`"
payload=$(jq -n \
--arg content "${PR_URL}" \
--arg thread "${THREAD_TITLE:0:99}" \
'{
content: $content,
thread_name: $thread,
auto_archive_duration: 10080
}'
)
curl -H "Content-Type: application/json" \
-X POST \
-d "$payload" \
"$WEBHOOK_URL"
fi
payload=$(jq -n \
--arg content "${PR_URL}" \
--arg thread "#${PR_NUMBER}: $PR_TITLE by \`${PR_AUTHOR}\`" \
'{
content: $content,
thread_name: $thread,
auto_archive_duration: 10080
}'
)
curl -H "Content-Type: application/json" \
-X POST \
-d "$payload" \
"$WEBHOOK_URL"
merge_success_emoji:
runs-on: ubuntu-latest
@@ -105,7 +84,7 @@ jobs:
fi
# 2) get the first message in that thread
messages=$(curl -H "Authorization: Bot $BOT_TOKEN" \
"https://discord.com/api/v10/channels/$thread_id/messages")
"https://discord.com/api/v10/channels/$thread_id/messages?limit=1")
message_id=$(echo "$messages" | jq -r '.[-1].id')
if [ -z "$message_id" ]; then

View File

@@ -1,34 +0,0 @@
name: Validate OpenAPI Spec
on:
push:
paths:
- 'backend/windmill-api/openapi*'
pull_request:
paths:
- 'backend/windmill-api/openapi*'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install openapi-generator-cli
run: npm install @openapitools/openapi-generator-cli -g
- name: Validate openapi.yaml
run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi.yaml
- name: Validate openapi-deref.json
run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi-deref.json
# Does not work well with dereferenced yaml
# - name: Validate openapi-deref.yaml
# run: npx @openapitools/openapi-generator-cli validate -i backend/windmill-api/openapi-deref.yaml

View File

@@ -1,152 +1,5 @@
# Changelog
## [1.496.3](https://github.com/windmill-labs/windmill/compare/v1.496.2...v1.496.3) (2025-06-09)
### Bug Fixes
* improve concurrent job parallelism performance ([e8836a3](https://github.com/windmill-labs/windmill/commit/e8836a393a872bb91e68ba0037681caf24149470))
* Prioritize diff contexts in script mode for ai chat ([#5888](https://github.com/windmill-labs/windmill/issues/5888)) ([a47939d](https://github.com/windmill-labs/windmill/commit/a47939d13c30e2d4b41efd539f845959174d4fb1))
## [1.496.2](https://github.com/windmill-labs/windmill/compare/v1.496.1...v1.496.2) (2025-06-07)
### Bug Fixes
* add clearable by default for select ([#5900](https://github.com/windmill-labs/windmill/issues/5900)) ([b44b9c1](https://github.com/windmill-labs/windmill/commit/b44b9c1b82116ad5487af95d1f78226d56c75179))
## [1.496.1](https://github.com/windmill-labs/windmill/compare/v1.496.0...v1.496.1) (2025-06-07)
### Bug Fixes
* never consider minor version for global site packages ([#5893](https://github.com/windmill-labs/windmill/issues/5893)) ([22b2f49](https://github.com/windmill-labs/windmill/commit/22b2f4988db9314f2403508933d0aa932187c668))
## [1.496.0](https://github.com/windmill-labs/windmill/compare/v1.495.1...v1.496.0) (2025-06-06)
### Features
* generate http route triggers from openapi spec ([#5857](https://github.com/windmill-labs/windmill/issues/5857)) ([5713483](https://github.com/windmill-labs/windmill/commit/571348377b73d54b4d2a1c5775ab00b247b01910))
### Bug Fixes
* allow fileupload drag and drop in edit mode on full component without triggering file picker ([#5889](https://github.com/windmill-labs/windmill/issues/5889)) ([9ae3212](https://github.com/windmill-labs/windmill/commit/9ae3212a1e0f88a8297bf41ab53e3c1be4bcc56c))
* **python:** account instance version when cli deploy and local lockfile ([#5894](https://github.com/windmill-labs/windmill/issues/5894)) ([ec552d5](https://github.com/windmill-labs/windmill/commit/ec552d5ef6fdb5e824e453f196f9cf16629ee2ea))
* use full client side js library for route gen from openapi ([#5891](https://github.com/windmill-labs/windmill/issues/5891)) ([3c3fdbd](https://github.com/windmill-labs/windmill/commit/3c3fdbdf26a9581b815210839b91ebdedb924093))
## [1.495.0](https://github.com/windmill-labs/windmill/compare/v1.494.0...v1.495.0) (2025-06-05)
### Features
* Add ask mode to AI chat ([#5878](https://github.com/windmill-labs/windmill/issues/5878)) ([67ab469](https://github.com/windmill-labs/windmill/commit/67ab46990ad0c9fad810a64c54297419c6151c79))
* add navigator mode to AIChat and unify UI ([#5859](https://github.com/windmill-labs/windmill/issues/5859)) ([cbba829](https://github.com/windmill-labs/windmill/commit/cbba8297cd4c1caa21b96a8422bbbd5c306b8398))
* ai flow chat ([#5842](https://github.com/windmill-labs/windmill/issues/5842)) ([68ebf66](https://github.com/windmill-labs/windmill/commit/68ebf667d5c0bc306329d0b55a3cc59e5b4862cb))
* ai prompts improvements + o3/o4 support ([#5862](https://github.com/windmill-labs/windmill/issues/5862)) ([825422c](https://github.com/windmill-labs/windmill/commit/825422c48456b2c9b230e1a35914b3fbf7d1e836))
* connect fix btn in flow editor to ai chat ([#5863](https://github.com/windmill-labs/windmill/issues/5863)) ([6247d15](https://github.com/windmill-labs/windmill/commit/6247d159ce25ae13f6fbc5c105df88305ce29451))
* fix backward compatibility pg 14 for postgres trigger ([#5851](https://github.com/windmill-labs/windmill/issues/5851)) ([4cbcbdb](https://github.com/windmill-labs/windmill/commit/4cbcbdb960b469acf773d3943128b6c7d0dcb0b8))
* ssh repl like direct to workers hosts machine ([#5809](https://github.com/windmill-labs/windmill/issues/5809)) ([f252657](https://github.com/windmill-labs/windmill/commit/f2526571a3614156b2b1e5cc91b15d0c57565d99))
* use rust-postgres client instead of sqlx for postgres trigger ([#5853](https://github.com/windmill-labs/windmill/issues/5853)) ([39dbd64](https://github.com/windmill-labs/windmill/commit/39dbd646b9683e0ad8de047cca786ae468759e77))
### Bug Fixes
* broken event dispatch for simpleditor ([#5879](https://github.com/windmill-labs/windmill/issues/5879)) ([df4992a](https://github.com/windmill-labs/windmill/commit/df4992a9295ed188c2a2cb0a5dfd3e33ae2e2dcb))
* cannot parse INSTANCE_PYTHON_VERSION ([#5874](https://github.com/windmill-labs/windmill/issues/5874)) ([a0b302d](https://github.com/windmill-labs/windmill/commit/a0b302d2c58d4245260376cf280bc866be91717c))
* fix regex that extract workspaces from custom tags ([#5876](https://github.com/windmill-labs/windmill/issues/5876)) ([1551dc8](https://github.com/windmill-labs/windmill/commit/1551dc8af22f6ea41f68290ace4c58f936c47745))
* nit ai flow prompt ([#5867](https://github.com/windmill-labs/windmill/issues/5867)) ([3e769f0](https://github.com/windmill-labs/windmill/commit/3e769f0c591b80138b3a356d147228675756452f))
* **python:** assign PATCH version to python runtime only when needed ([#5866](https://github.com/windmill-labs/windmill/issues/5866)) ([50a5c1f](https://github.com/windmill-labs/windmill/commit/50a5c1f56a7e45882fa0095203de709571e149bb))
* remove duplicate tools from script ai chat ([#5880](https://github.com/windmill-labs/windmill/issues/5880)) ([fe4a767](https://github.com/windmill-labs/windmill/commit/fe4a767df0e6f46fd0c0fd21b4116c7375978bf9))
* replace crypto.randomUUID with generateRandomString for HTTP compatibility ([#5849](https://github.com/windmill-labs/windmill/issues/5849)) ([64f35d0](https://github.com/windmill-labs/windmill/commit/64f35d050fb0d1008ce7142fd62d500845e62c4a)), closes [#5847](https://github.com/windmill-labs/windmill/issues/5847)
## [1.494.0](https://github.com/windmill-labs/windmill/compare/v1.493.4...v1.494.0) (2025-05-31)
### Features
* array of s3 objects in input maker ([806d669](https://github.com/windmill-labs/windmill/commit/806d66972568d21a1621acd1b30db5ae9b217341))
* **rust:** shared build directory ([#5610](https://github.com/windmill-labs/windmill/issues/5610)) ([ed61d97](https://github.com/windmill-labs/windmill/commit/ed61d9770031c1a04908880dbd3e5fb692df9946))
### Bug Fixes
* allow disable tabs for sidebar/accordion tabs ([#5838](https://github.com/windmill-labs/windmill/issues/5838)) ([80277d1](https://github.com/windmill-labs/windmill/commit/80277d14d02e8e596c7002326946142226d382a6))
## [1.493.4](https://github.com/windmill-labs/windmill/compare/v1.493.3...v1.493.4) (2025-05-29)
### Bug Fixes
* templatev2 delete issue ([#5834](https://github.com/windmill-labs/windmill/issues/5834)) ([ed3ad32](https://github.com/windmill-labs/windmill/commit/ed3ad327a235c16b9f3aa7f8edeefe61b0c01da3))
## [1.493.3](https://github.com/windmill-labs/windmill/compare/v1.493.2...v1.493.3) (2025-05-29)
### Bug Fixes
* evalv2 prohibit component delete ([e302aa3](https://github.com/windmill-labs/windmill/commit/e302aa38b5977dd406ae05e1d8dbb74cb7dc3d17))
* faster layout for larger graphs ([8d12bcc](https://github.com/windmill-labs/windmill/commit/8d12bcc8ee2991909ea0d9bb57f04f0d4106c69f))
## [1.493.2](https://github.com/windmill-labs/windmill/compare/v1.493.1...v1.493.2) (2025-05-28)
### Bug Fixes
* improve monaco editor memory leak ([e0f4f83](https://github.com/windmill-labs/windmill/commit/e0f4f83ebf4416c3bcc24433a7bf606349e1f75a))
* improve monaco javascript extra lib refresh ([7b70348](https://github.com/windmill-labs/windmill/commit/7b70348b4bba3726e3fb26c964219a5a2aa6af55))
## [1.493.1](https://github.com/windmill-labs/windmill/compare/v1.493.0...v1.493.1) (2025-05-28)
### Bug Fixes
* improve monaco javascript extra lib refresh ([a2c8ea6](https://github.com/windmill-labs/windmill/commit/a2c8ea69a3962a350273717cd237d8a96523fd00))
## [1.493.0](https://github.com/windmill-labs/windmill/compare/v1.492.1...v1.493.0) (2025-05-27)
### Features
* add aws oidc support for instance s3 storage ([#5810](https://github.com/windmill-labs/windmill/issues/5810)) ([5b96bcc](https://github.com/windmill-labs/windmill/commit/5b96bccedd6e68fea631580dd49338301ad0305f))
* duckdb sql lang support ([#5761](https://github.com/windmill-labs/windmill/issues/5761)) ([fdefd4b](https://github.com/windmill-labs/windmill/commit/fdefd4be9398b9610a539360353fd61b521732d4))
* **python:** inline script metadata (PEP 723) ([#5712](https://github.com/windmill-labs/windmill/issues/5712)) ([2622253](https://github.com/windmill-labs/windmill/commit/26222539e66bce7e88f86a7e5917e6ca99350865))
### Bug Fixes
* add missing http_trigger_version_seq grants ([#5816](https://github.com/windmill-labs/windmill/issues/5816)) ([306f3ea](https://github.com/windmill-labs/windmill/commit/306f3eabd1c03fa904b0e59438de124a0e680597))
* avoid monaco memory leak ([0d459d5](https://github.com/windmill-labs/windmill/commit/0d459d5d223728270854e37715ecc1663ede9870))
* error handler node rendering at top level ([feae9b0](https://github.com/windmill-labs/windmill/commit/feae9b09240ba306c007013a36d2aefb0b273766))
* **frontend:** auto completion and render of tailwind classes in app editor ([#5817](https://github.com/windmill-labs/windmill/issues/5817)) ([5897e7e](https://github.com/windmill-labs/windmill/commit/5897e7e01b8839425c30c2a97481ef7bb9090661))
## [1.492.1](https://github.com/windmill-labs/windmill/compare/v1.492.0...v1.492.1) (2025-05-22)
### Bug Fixes
* fix strum compile ([59f6024](https://github.com/windmill-labs/windmill/commit/59f6024cbdaface9c9f0ed61c4a415a13b558515))
## [1.492.0](https://github.com/windmill-labs/windmill/compare/v1.491.5...v1.492.0) (2025-05-22)
### Features
* job search pagination + result count ([#5789](https://github.com/windmill-labs/windmill/issues/5789)) ([55ae766](https://github.com/windmill-labs/windmill/commit/55ae76648475ce9ff14b2fa33b2a71b90fbd50a1))
* **python:** add annotation to skip result post-processing ([#5769](https://github.com/windmill-labs/windmill/issues/5769)) ([07c2ff5](https://github.com/windmill-labs/windmill/commit/07c2ff5668f4725a3b9a8a2655248b0945ac251c))
* shift/ctrl+click/enter to open ctrl+k menu results in new tab ([#5800](https://github.com/windmill-labs/windmill/issues/5800)) ([66a997a](https://github.com/windmill-labs/windmill/commit/66a997afc399de2d592c469faf9a5b2cd6433aac))
* triggers git sync ([#5766](https://github.com/windmill-labs/windmill/issues/5766)) ([065a814](https://github.com/windmill-labs/windmill/commit/065a814d35a5749725c2ada1155481abba782684))
### Bug Fixes
* improve app css consistency ([88482c3](https://github.com/windmill-labs/windmill/commit/88482c3bd76ddad16738354f7531d16fa806ad2f))
* improve docker mode unexpected exit handling ([7c24fbc](https://github.com/windmill-labs/windmill/commit/7c24fbcef2ecfe5fc034870c4c65dd80513301a4))
* postgres trigger ssl issue ([#5790](https://github.com/windmill-labs/windmill/issues/5790)) ([b9a776c](https://github.com/windmill-labs/windmill/commit/b9a776c97b3411af18e58cde7a070c4955aaaab4))
* specify using inline type in system prompt for AI ([#5787](https://github.com/windmill-labs/windmill/issues/5787)) ([791296f](https://github.com/windmill-labs/windmill/commit/791296fa41c5bc45c32944db8bc1b66e1515ea82))
* workspace preprocessor improvements ([#5784](https://github.com/windmill-labs/windmill/issues/5784)) ([30edcdf](https://github.com/windmill-labs/windmill/commit/30edcdfe0e950b0ab850942bcbc9b4b5ff4fc00c))
## [1.491.5](https://github.com/windmill-labs/windmill/compare/v1.491.4...v1.491.5) (2025-05-17)

View File

@@ -1,3 +1,71 @@
To have an overview of what this app does, see @.cursor/rules/windmill-overview.mdc
For backend modifications, follow the rules mentioned here @.cursor/rules/rust-best-practices.mdc
For frontend modifications, follow the rules mentioned here @.cursor/rules/svelte5-best-practices.mdc
# Windmill Overview
Windmill is an open-source developer platform for building internal tools, API integrations, background jobs, workflows, and user interfaces. It offers a unified system where scripts are automatically turned into sharable UIs and can be composed into flows or embedded in custom applications.
## Core Capabilities
- **Script Development and Execution**: Write and run scripts in Python, TypeScript/JavaScript (Deno/Bun), Go, Bash, SQL, and other languages
- **Workflow Orchestration**: Compose scripts into multi-step flows with conditional logic, loops, and error handling
- **UI Generation**: Automatically generate UIs from scripts or build custom applications with a low-code editor
- **Job Scheduling**: Trigger scripts and flows on schedules, webhooks, or external events
- **Resource Management**: Securely store and use credentials, databases, and other connections
## Platform Architecture
The Windmill platform consists of several key components:
- **Frontend UI**: Web-based interface for script and flow development, app building, and result visualization
- **API Server**: Central API that handles authentication, resource management, and job coordination
- **Workers**: Execute scripts in their respective environments with proper sandboxing
- **Database**: PostgreSQL database for storage of scripts, flows, resources, job results, and more
- **Job Queue**: Queue system for managing job execution, implemented in PostgreSQL
- **Client Libraries**: Libraries for interacting with Windmill from Python, TypeScript, or command line
# Windmill Backend Architecture
The Windmill backend is written in Rust and consists of several services working together. These services are designed for horizontal scaling with stateless API servers and workers that can be deployed across multiple machines.
## Key Components
- **API Server (`windmill-api`)**: Handles HTTP requests, authentication, and resource management
- **Queue Manager (`windmill-queue`)**: Manages the job queue in PostgreSQL
- **Worker System (`windmill-worker`)**: Executes jobs in sandboxed environments
- **Common Utilities (`windmill-common`)**: Shared code used by multiple services
- **Git Sync (`windmill-git-sync`)**: Synchronizes scripts with Git repositories
## Job Execution System
The job execution process follows these steps:
1. The API server receives a request to run a script or flow and creates a job record in the database
2. The job is added to the queue system in PostgreSQL
3. Workers continuously poll the queue for jobs matching their capabilities
4. When a job is picked up, it's routed to the appropriate language executor
5. The script is executed in a sandboxed environment using NSJAIL for security
6. Results are processed and stored in the database
7. For flows, each step creates a new job that goes through the same process
Windmill supports worker tags and groups to route jobs to workers with specific capabilities or resource access.
# Windmill Frontend Architecture
The Windmill frontend is built with Svelte and provides several key interfaces for interacting with the platform.
## Key Components
- **Script Builder**: Code editor with language support, schema inference, and dependency management
- **Flow Builder**: Visual editor for creating multi-step workflows with branching and looping
- **App Editor**: Grid-based editor for building custom UIs that integrate scripts and flows
- **Schema Form System**: Generates form interfaces from script parameters automatically
- **Result Viewer**: Visualizes job results, logs, and execution status
The frontend uses the Monaco editor (same as VS Code) for code editing, with specialized language support for all supported script languages.
## UI Framework
The frontend is built with Svelte, providing a reactive and component-based architecture. Key frontend technologies include:
- **Svelte/SvelteKit**: Core framework for UI components and routing
- **Monaco Editor**: Code editing experience similar to VS Code
- **Schema Form**: Automatic UI generation from TypeScript/JSON schemas
- **Tailwind CSS**: Utility-first CSS framework for styling

4
backend/.gitignore vendored
View File

@@ -5,6 +5,4 @@ oauth2.json
tracing.folded
heaptrack*
index/
windmill-api/openapi-*.*
.duckdb/*
*ee.rs
windmill-api/openapi-*.*

View File

@@ -1 +0,0 @@
!*ee.rs

View File

@@ -61,8 +61,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -1,24 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM sqs_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
null
]
},
"hash": "13444bbd5547e101c41206c5f97ac4dded0536faf52c370d704ed9a451041caf"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "CREATE INDEX CONCURRENTLY IF NOT EXISTS v2_job_queue_suspend ON v2_job_queue (workspace_id, suspend) WHERE suspend > 0;",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "19f0ccadd3ee44719a781ea0d73ea4e45f5b2c3d5c0aa5dbecf9ea9838881b74"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int4"
]
},
"nullable": []
},
"hash": "1d87f41fd1abb9361d795a899120e6b77e24bf5a9044fdc5284d0d7f1e14eafa"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int4"
]
},
"nullable": []
},
"hash": "2bf5f7f2cf9d85a5d23e5db2f7616fb41fece9b3d46fde2d546d70b46f9008e3"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "usage",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SHOW WAL_LEVEL;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "wal_level",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "2ef25599ea0c9ef946d6cc70ae048af970aed2638a3f767e152b654aebf68e48"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"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\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')),\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\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC, devops DESC\n LIMIT $1 OFFSET $2",
"describe": {
"columns": [
{
@@ -67,5 +67,5 @@
true
]
},
"hash": "5430f7728c1e9b539cc8aad29ca9e6733943278998d3df62a9486607827e59ec"
"hash": "3895cee539a24b4c6ea89fa7a835fc62bc93b0530efba09fc3c32a8f93eaabb1"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT pubname AS publication_name FROM pg_publication;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "publication_name",
"type_info": "Name"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "4469ee6c206c46951980ea1bc73f126f339d2e3cf97f363be8921084b16dac45"
}

View File

@@ -70,8 +70,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -137,8 +137,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -1,58 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n tag, \n script_lang AS \"script_lang!: _\"\n FROM \n v2_job\n WHERE \n id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "tag",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "script_lang!: _",
"type_info": {
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
]
}
}
}
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false,
true
]
},
"hash": "4e5273b9ce05f6ee2dfd5f14c8574a0cf43682480452f7dbe23012320fe7fe25"
}

View File

@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n slot_name,\n active\n FROM\n pg_replication_slots \n WHERE \n plugin = 'pgoutput' AND\n slot_type = 'logical';\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "slot_name",
"type_info": "Name"
},
{
"ordinal": 1,
"name": "active",
"type_info": "Bool"
}
],
"parameters": {
"Left": []
},
"nullable": [
true,
true
]
},
"hash": "4ee0017771f46f0272817d18edb821940cb5064e3f155b9630b131c09c9dba13"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2",
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2",
"describe": {
"columns": [],
"parameters": {
@@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1"
"hash": "4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT fv.id\n FROM flow f\n INNER JOIN flow_version fv ON fv.id = f.versions[array_upper(f.versions, 1)]\n WHERE fv.value->'preprocessor_module'->'value'->>'path' = $1 AND f.workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE parent_job = $1\n AND f.id = j.id AND q.id = j.id\n AND suspend = $2 AND (f.flow_status->'step')::int = 0",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Int4"
]
},
"nullable": []
},
"hash": "553108ba3c0b8d579800bc8b5a4f887d79fb4c13b60b19c4913a8db18521958c"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "usage",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "56b2326015fde12b1a4efa226518566101dd27a0f3363884781071d417f8b7e7"
}

View File

@@ -61,8 +61,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "usage",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e"
}

View File

@@ -1,24 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM gcp_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
null
]
},
"hash": "6a19c440a7a8064f3969cf6f48adea0bfdb683de9555e374ce5731e0b3c379f9"
}

View File

@@ -147,8 +147,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT slot_name FROM pg_replication_slots where slot_name = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "slot_name",
"type_info": "Name"
}
],
"parameters": {
"Left": [
"Name"
]
},
"nullable": [
true
]
},
"hash": "6f56acb985aa7141ea1891d7ad58a32c35d1b02fe7070c92a2e62c1a5339c396"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE \n path = $1\n AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1",
"query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow \n WHERE workspace_id = $1\n AND path = $2",
"describe": {
"columns": [
{
@@ -25,5 +25,5 @@
true
]
},
"hash": "e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d"
"hash": "72916f8e490f8252e0a51b7f562ccc3be832b12102eb86a07d8405a4fa9287d5"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT \n active_pid \n FROM \n pg_replication_slots \n WHERE \n slot_name = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "active_pid",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Name"
]
},
"nullable": [
true
]
},
"hash": "7e64ba7e2362cc19d2aed9f34c9879983922e96a9baab7c1a2b09ed2b1c261e2"
}

View File

@@ -34,8 +34,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT distinct(path) FROM script WHERE workspace_id = $1 AND archived = true",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "8373b2649ab46310860adbdd7b717261771ac61d46d82d42d085ffebeb18be06"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "usage",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "83f64dd93b1ddc03b84681d65d9be69959987cbac1d83b64225fd1bf9ab047c9"
}

View File

@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n puballtables AS all_table,\n pubinsert AS insert,\n pubupdate AS update,\n pubdelete AS delete\n FROM\n pg_publication\n WHERE\n pubname = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "all_table",
"type_info": "Bool"
},
{
"ordinal": 1,
"name": "insert",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "update",
"type_info": "Bool"
},
{
"ordinal": 3,
"name": "delete",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Name"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "86ae16175ace0179e784aacfd381771f0137ecab6671d632febadede729e7783"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE worker_ping SET\nping_at = now(),\njobs_executed = 1,\ncurrent_job_id = $1,\ncurrent_job_workspace_id = 'admins'\nWHERE worker = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": []
},
"hash": "997586ac14384db2c0eeee1bb3382cc6ae013695d0cda6da9ab848ca1b9a9606"
}

View File

@@ -1,24 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM mqtt_trigger WHERE script_path = $1 AND is_flow = $2 AND workspace_id = $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Text"
]
},
"nullable": [
null
]
},
"hash": "a8b470b463ca4b7c00c7ef6e9f36c23f8bbcefc288a56d61122bfd6fe5ca7e8d"
}

View File

@@ -36,8 +36,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -41,8 +41,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -65,8 +65,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT pubname FROM pg_publication WHERE pubname = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "pubname",
"type_info": "Name"
}
],
"parameters": {
"Left": [
"Name"
]
},
"nullable": [
false
]
},
"hash": "baa1dddc616419bf4b923715f0a863bc0ff69c98db0f0c8f55e4ac89fdde7a60"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE worker_ping SET \nping_at = now(), \njobs_executed = 1, \ncurrent_job_id = $1, \ncurrent_job_workspace_id = 'admins' \nWHERE worker = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": []
},
"hash": "c3025cdb6e421e1225d420e8b1efd18d1dd3bb2fac53c1f2df648b61fb7488aa"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int4"
]
},
"nullable": []
},
"hash": "ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8"
}

View File

@@ -1,38 +0,0 @@
{
"db_name": "PostgreSQL",
"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 active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "authors",
"type_info": "VarcharArray"
},
{
"ordinal": 1,
"name": "operators",
"type_info": "VarcharArray"
},
{
"ordinal": 2,
"name": "author_count",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "operator_count",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null,
null
]
},
"hash": "cb3862634f18160207ee2621ddfca43f00456a27fda32583846497116f92f96c"
}

View File

@@ -70,8 +70,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -0,0 +1,38 @@
{
"db_name": "PostgreSQL",
"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')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "authors",
"type_info": "VarcharArray"
},
{
"ordinal": 1,
"name": "operators",
"type_info": "VarcharArray"
},
{
"ordinal": 2,
"name": "author_count",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "operator_count",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null,
null,
null,
null
]
},
"hash": "cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c"
}

View File

@@ -42,8 +42,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int4"
]
},
"nullable": []
},
"hash": "e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c"
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE q.workspace_id = $1 AND q.suspend = $3 AND j.parent_job = $2\n AND f.id = j.id AND q.id = j.id\n AND (f.flow_status->'step')::int = 0",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Int4"
]
},
"nullable": []
},
"hash": "f1dbcb6e6d82d17c19eb88c0e67dc1cb8baf5bd40b75a2a9cd3ebac440fda632"
}

View File

@@ -147,8 +147,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -1,22 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path FROM script WHERE workspace_id = $1 AND archived = false",
"query": "SELECT tag FROM v2_job WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"name": "tag",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
"Uuid"
]
},
"nullable": [
false
]
},
"hash": "3e244a5057d4f1b4a18c0edac52cdf695c7e7aa0468d2686255de3d83719e6d0"
"hash": "faf2c77242e0ab39b33886edf3b742531bf1351d0be1c3631bde0adfe375497a"
}

View File

@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n schemaname AS schema_name,\n tablename AS table_name,\n CASE\n WHEN array_length(attnames, 1) = (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = pg_publication_tables.schemaname AND table_name = pg_publication_tables.tablename)\n THEN NULL\n ELSE attnames\n END AS columns,\n rowfilter AS where_clause\n FROM\n pg_publication_tables\n WHERE\n pubname = $1;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "schema_name",
"type_info": "Name"
},
{
"ordinal": 1,
"name": "table_name",
"type_info": "Name"
},
{
"ordinal": 2,
"name": "columns",
"type_info": "NameArray"
},
{
"ordinal": 3,
"name": "where_clause",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Name"
]
},
"nullable": [
true,
true,
null,
true
]
},
"hash": "fd5754fe3c6346ae28818a9d60d144a40f8884f47e5bbdd2824e939dafd8f154"
}

View File

@@ -65,8 +65,7 @@
"csharp",
"oracledb",
"nu",
"java",
"duckdb"
"java"
]
}
}

View File

@@ -11,7 +11,5 @@
"remote.autoForwardPorts": true,
"conventionalCommits.scopes": [
"restructring triggers, decoding trigger message on work"
],
"files.exclude": { "**/*ee.rs": false },
"search.exclude": { "**/*ee.rs": false }
]
}

104
backend/CLAUDE.md Normal file
View File

@@ -0,0 +1,104 @@
# Windmill Backend - Rust Best Practices
## Project Structure
Windmill uses a workspace-based architecture with multiple crates:
- **windmill-api**: API server functionality
- **windmill-worker**: Job execution
- **windmill-common**: Shared code used by all crates
- **windmill-queue**: Job & flow queuing
- **windmill-audit**: Audit logging
- Other specialized crates (git-sync, autoscaling, etc.)
## Adding New Code
### Module Organization
- Place new code in the appropriate crate based on functionality
- For API endpoints, create or modify files in `windmill-api/src/` organized by domain
- For shared functionality, use `windmill-common/src/`
- Use the `_ee.rs` suffix for enterprise-only modules
- Follow existing patterns for file structure and organization
### Error Handling
- Use the custom `Error` enum from `windmill-common::error`
- Return `Result<T, Error>` or `JsonResult<T>` for functions that can fail
- Use the `?` operator for error propagation
- Add location tracking to errors using `#[track_caller]`
### Database Operations
- Use `sqlx` for database operations with prepared statements
- Leverage existing database helper functions in `db.rs` modules
- Use transactions for multi-step operations
- Handle database errors properly
### API Endpoints
- Follow existing patterns in the `windmill-api` crate
- Use axum's routing system and extractors
- Group related routes together
- Use consistent response formats (JSON)
- Follow proper authentication and authorization patterns
## Performance Optimizations
When generating code, especially involving `serde`, `sqlx`, and `tokio`, prioritize performance by applying the following principles:
### Serde Optimizations (Serialization & Deserialization)
- **Specify Structure Explicitly:** When defining structs for Serde (`#[derive(Serialize, Deserialize)]`), use `#[serde(...` attributes extensively. This includes:
- `#[serde(rename = "...")]` or `#[serde(alias = "...")]` to map external names precisely, avoiding dynamic lookups.
- `#[serde(default)]` for optional fields with default values, reducing parsing complexity.
- `#[serde(skip_serializing_if = "...")]` to avoid writing fields that meet a certain condition (e.g., `Option::is_none()`, `Vec::is_empty()`, or a custom function), reducing output size and serialization work.
- `#[serde(skip_serializing)]` or `#[serde(skip_deserializing)]` for fields that should _not_ be included.
- **Prefer Borrowing:** Where possible and safe (data lifetime allows), use `Cow<'a, str>` or `&'a str` (with `#[serde(borrow)]`) instead of `String` for string fields during deserialization. This avoids allocating new strings, enabling zero-copy reading from the input buffer. Apply this principle to byte slices (`&'a [u8]` / `Cow<'a, [u8]>`) and potentially borrowed vectors as well.
- **Avoid Intermediate `Value`:** Unless the data structure is truly dynamic or unknown at compile time, deserialize directly into a well-defined struct or enum rather than into `serde_json::Value` (or equivalent for other formats). This avoids unnecessary heap allocations and type switching.
### SQLx Optimizations (Database Interaction)
- **Select Only Necessary Columns:** In `SELECT` queries, list specific column names rather than using `SELECT *`. This reduces data transferred from the database and the work needed for hydration/deserialization.
- **Batch Operations:** For multiple `INSERT`, `UPDATE`, or `DELETE` statements, prefer executing them in a single query if the database and driver support it efficiently (e.g., `INSERT INTO ... VALUES (...), (...), ...`). This minimizes round trips to the database.
- **Avoid N+1 Queries:** Do not loop through results of one query and execute a separate query for each item (e.g., fetching users, then querying for each user's profile in a loop). Instead, use JOINs or a single query with an `IN` clause to fetch related data efficiently.
- **Deserialize Directly:** Use `#[derive(FromRow)]` on structs and ensure the struct fields match the selected columns in the query. This allows SQLx to hydrate objects directly, avoiding intermediate data structures.
- **Parameterize Queries:** Always use SQLx's query methods (`.bind(...)`) to pass values as parameters rather than string formatting. This prevents SQL injection and allows the database to cache query plans, improving performance on repeated executions.
### Tokio Optimizations (Asynchronous Runtime)
- **Avoid Blocking Operations:** **Crucially**, never perform blocking operations (synchronous file I/O, `std::thread::sleep`, CPU-bound loops, `std::sync::Mutex::lock`, blocking network calls without `tokio::net`) directly within an `async fn` or a standard `tokio::spawn` task. Blocking pauses the entire worker thread, potentially starving other tasks. Use `tokio::task::spawn_blocking` for CPU-intensive work or blocking I/O.
- **Use Tokio's Async Primitives:** Prefer `tokio::sync` (channels, mutexes, semaphores), `tokio::io`, `tokio::net`, and `tokio::time` over their `std` counterparts in asynchronous contexts. These are designed to yield control back to the scheduler.
- **Manage Concurrency:** Be mindful of how many tasks are spawned. Creating a new task for every tiny piece of work can introduce overhead. Group related asynchronous operations where appropriate.
- **Handle Shared State Efficiently:** Use `Arc` for shared ownership in concurrent tasks. When shared state needs mutation, prefer `tokio::sync::Mutex` over `std::sync::Mutex` in `async` code. Consider `tokio::sync::RwLock` if reads significantly outnumber writes. Minimize the duration for which locks are held.
- **Understand `.await`:** Place `.await` strategically to allow the runtime to switch to other ready tasks. Ensure that `.await` points to genuinely asynchronous operations.
- **Backpressure:** If dealing with data streams or queues between tasks, implement backpressure mechanisms (e.g., bounded channels like `tokio::sync::mpsc::channel`) to prevent one component from overwhelming another or critical resources like the database.
## Enterprise Features
- Use feature flags for enterprise functionality
- Conditionally compile with `#[cfg(feature = "enterprise")]`
- Isolate enterprise code in separate modules
## Code Style
- Group imports by external and internal crates
- Place struct/enum definitions before implementations
- Group similar functionality together
- Use descriptive naming consistent with the codebase
- Follow existing patterns for async code using tokio
## Testing
- Write unit tests for core functionality
- Use the `#[cfg(test)]` module for test code
- For database tests, use the existing test utilities
## Common Crates Used
- **tokio**: For async runtime
- **axum**: For web server and routing
- **sqlx**: For database operations
- **serde**: For serialization/deserialization
- **tracing**: For logging and diagnostics
- **reqwest**: For HTTP client functionality

699
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.496.3"
version = "1.491.5"
authors.workspace = true
edition.workspace = true
@@ -32,7 +32,7 @@ members = [
]
[workspace.package]
version = "1.496.3"
version = "1.491.5"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -49,7 +49,6 @@ lto = "thin"
[features]
default = []
private = ["windmill-api/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private"]
agent_worker_server = ["windmill-api/agent_worker_server"]
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
enterprise_saml = ["windmill-api/enterprise_saml", "oauth2"]
@@ -60,7 +59,7 @@ embedding = ["windmill-api/embedding"]
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "dep:object_store"]
prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus", "dep:prometheus"]
flow_testing = ["windmill-worker/flow_testing"]
openidconnect = ["windmill-api/openidconnect", "windmill-common/openidconnect"]
openidconnect = ["windmill-api/openidconnect"]
cloud = ["windmill-queue/cloud", "windmill-worker/cloud", "windmill-common/cloud", "windmill-api/cloud"]
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "windmill-common/tantivy", "enterprise", "parquet"]
@@ -84,21 +83,18 @@ zip = ["windmill-api/zip"]
static_frontend = ["windmill-api/static_frontend"]
scoped_cache = ["windmill-common/scoped_cache"]
# Languages
python = ["windmill-worker/python", "windmill-api/python"]
python = ["windmill-worker/python"]
rust = ["windmill-worker/rust"]
mysql = ["windmill-worker/mysql"]
oracledb = ["windmill-worker/oracledb"]
duckdb = ["windmill-worker/duckdb"]
mssql = ["windmill-worker/mssql"]
bigquery = ["windmill-worker/bigquery"]
php = ["windmill-worker/php"]
csharp = ["windmill-worker/csharp"]
nu = ["windmill-worker/nu"]
java = ["windmill-worker/java"]
all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
# For windows we have another set of languages enabled
# NOTE: DuckDB is ignored because of compilation problems
all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
all_languages = [ "python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
[patch.crates-io]
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
@@ -139,11 +135,8 @@ quote.workspace = true
memchr.workspace = true
v8 = { workspace = true, optional = true }
rustls.workspace = true
pep440_rs.workspace = true
systemstat.workspace = true
size.workspace = true
strum.workspace = true
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { optional = true, workspace = true }
@@ -225,7 +218,6 @@ git-version = "^0"
malachite = "=0.4.18"
malachite-bigint = "=0.2.0"
rustpython-parser = "^0"
pep440_rs = "0.7.3"
php-parser-rs = { git = "https://github.com/php-rust-tools/parser", rev = "ec4cb411dec09450946ef57920b7ffced7f6495d" }
cron = "^0"
mail-send = { version = "0.4.0", features = ["builder"], default-features=false }
@@ -242,7 +234,6 @@ json-pointer = "^0"
itertools = "^0"
regex = "^1"
semver = "^1"
duckdb = { version = "1.2.2", features = ["bundled"] }
v8 = "=130.0.7" # Exact version NOTE: Do not forget to update version and hash in flake.nix
deno_fetch = "0.214.0"
@@ -351,7 +342,7 @@ openidconnect = { version = "4.0.0-rc.1" }
aws-config = "^1"
aws-sdk-sqs = "1.57.0"
aws-sdk-sts = "^1"
aws-smithy-types-convert = { version = "^0", features = ["convert-chrono"] }
crc = "^3"
tar = "^0"
http = "^1"
@@ -398,5 +389,3 @@ tree-sitter-c-sharp = "0.23.0"
tree-sitter-java = "0.23.0"
oracle = { version = "0.6.3", features = ["chrono"] }
rumqttc = { version = "0.24.0", features = ["use-native-tls"]}
strum = { version = "0.27", features = ["derive"] }
strum_macros = "^0"

View File

@@ -1,20 +0,0 @@
# This script outputs all features except private. Usage :
# > cargo build --features $(./all_features_oss.sh)
#!/bin/bash
# Path to the Cargo.toml file
CARGO_TOML_PATH="./Cargo.toml"
# Extract features from Cargo.toml and output them separated by commas
if [[ -f "$CARGO_TOML_PATH" ]]; then
grep -A 100 '\[features\]' "$CARGO_TOML_PATH" | \
sed -n '/\[features\]/,/^\[/p' | \
grep -E '^[a-zA-Z0-9_-]+' | \
grep -v 'private' | \
cut -d' ' -f1 | \
paste -sd ',' -
else
echo "Cargo.toml not found at $CARGO_TOML_PATH"
exit 1
fi

View File

@@ -1 +1 @@
2c3e21f4573486628e0b8969ff478c237bd0283f
11917062c8a5ea230f27fe750cbde1dbdc0512f9

View File

@@ -1 +0,0 @@
-- Add down migration script here

View File

@@ -1,3 +0,0 @@
-- Add up migration script here
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'duckdb';
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["duckdb"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust", "ansible", "csharp", "nu", "java"]}'::jsonb AND NOT config->'worker_tags' @> '"duckdb"'::jsonb;

View File

@@ -1,12 +0,0 @@
-- Add down migration script here
CREATE OR REPLACE FUNCTION notify_runnable_version_change()
RETURNS TRIGGER AS $$
DECLARE
source_type TEXT;
BEGIN
source_type := TG_ARGV[0];
PERFORM pg_notify('notify_runnable_version_change', NEW.workspace_id || ':' || source_type || ':' || NEW.path);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@@ -1,19 +0,0 @@
-- Add up migration script here
CREATE OR REPLACE FUNCTION notify_runnable_version_change()
RETURNS TRIGGER AS $$
DECLARE
source_type TEXT;
kind TEXT;
BEGIN
source_type := TG_ARGV[0];
IF source_type = 'script' THEN
kind := NEW.kind;
ELSE
kind := 'flow';
END IF;
PERFORM pg_notify('notify_runnable_version_change', NEW.workspace_id || ':' || source_type || ':' || NEW.path || ':' || kind);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@@ -1 +0,0 @@
-- Add down migration script here

View File

@@ -1,3 +0,0 @@
-- Add up migration script here
GRANT ALL ON SEQUENCE http_trigger_version_seq TO windmill_user;
GRANT ALL ON SEQUENCE http_trigger_version_seq TO windmill_admin;

View File

@@ -1,4 +0,0 @@
-- Remove token invalidation notification trigger
DROP TRIGGER IF EXISTS token_invalidation_trigger ON token;
DROP FUNCTION IF EXISTS notify_token_invalidation();

View File

@@ -1,17 +0,0 @@
-- Add token invalidation notification trigger
CREATE OR REPLACE FUNCTION notify_token_invalidation()
RETURNS TRIGGER AS $$
BEGIN
-- Only notify for session token deletions when the invalidation settings are enabled
IF OLD.label = 'session' AND OLD.email IS NOT NULL THEN
PERFORM pg_notify('notify_token_invalidation', OLD.token);
END IF;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER token_invalidation_trigger
AFTER DELETE ON token
FOR EACH ROW
EXECUTE FUNCTION notify_token_invalidation();

View File

@@ -27,6 +27,3 @@ anyhow.workspace = true
lazy_static.workspace = true
sqlx.workspace = true
async-recursion.workspace = true
toml.workspace = true
serde.workspace = true
pep440_rs.workspace = true

View File

@@ -11,7 +11,7 @@ mod mapping;
use async_recursion::async_recursion;
use itertools::Itertools;
use lazy_static::lazy_static;
use std::{collections::HashMap, str::FromStr};
use std::collections::HashMap;
use mapping::{FULL_IMPORTS_MAP, SHORT_IMPORTS_MAP};
#[cfg(not(target_arch = "wasm32"))]
@@ -25,10 +25,7 @@ use rustpython_parser::{
Parse,
};
use sqlx::{Pool, Postgres};
use windmill_common::{
error::{self, to_anyhow},
worker::PythonAnnotations,
};
use windmill_common::{error, worker::PythonAnnotations};
const DEF_MAIN: &str = "def main(";
@@ -245,7 +242,8 @@ pub async fn parse_python_imports(
w_id: &str,
path: &str,
db: &Pool<Postgres>,
version_specifiers: &mut Vec<pep440_rs::VersionSpecifier>,
already_visited: &mut Vec<String>,
annotated_pyv_numeric: &mut Option<u32>,
) -> error::Result<(Vec<String>, Option<String>)> {
let mut compile_error_hint: Option<String> = None;
let mut imports = parse_python_imports_inner(
@@ -253,10 +251,9 @@ pub async fn parse_python_imports(
w_id,
path,
db,
&mut vec![],
version_specifiers,
// &mut version_specifier.and_then(|_| Some(path.to_owned())),
&mut None
already_visited,
annotated_pyv_numeric,
&mut annotated_pyv_numeric.and_then(|_| Some(path.to_owned())),
)
.await?
.into_values()
@@ -282,7 +279,6 @@ pub async fn parse_python_imports(
.flatten()
.collect::<error::Result<Vec<String>>>()?
.into_iter()
.filter(|x| !x.trim_start().starts_with("--") && !x.trim().is_empty())
.unique()
.collect_vec();
@@ -308,34 +304,11 @@ async fn parse_python_imports_inner(
path: &str,
db: &Pool<Postgres>,
already_visited: &mut Vec<String>,
version_specifiers: &mut Vec<pep440_rs::VersionSpecifier>,
annotated_pyv_numeric: &mut Option<u32>,
path_where_annotated_pyv: &mut Option<String>,
) -> error::Result<HashMap<String, NImportResolved>> {
let PythonAnnotations { py310, py311, py312, py313, .. } = PythonAnnotations::parse(&code);
let mut push_version_specifiers = |perform, unparsed: String| -> error::Result<()> {
if perform {
pep440_rs::VersionSpecifiers::from_str(unparsed.as_str())
.ok()
.map(|vs| version_specifiers.extend(vs.to_vec()));
}
Ok(())
};
push_version_specifiers(py310, "==3.10.*".to_owned())?;
push_version_specifiers(py311, "==3.11.*".to_owned())?;
push_version_specifiers(py312, "==3.12.*".to_owned())?;
push_version_specifiers(py313, "==3.13.*".to_owned())?;
for x in code.lines() {
if x.starts_with("# py:") || x.starts_with("#py:") {
push_version_specifiers(
true,
x.replace('#', "").replace("py:", "").trim().to_owned(),
)?;
} else if !x.starts_with('#') {
break;
}
}
// we pass only if there is none or only one annotation
// Naive:
@@ -350,48 +323,39 @@ async fn parse_python_imports_inner(
// This way we make sure there is no multiple annotations for same script
// and we get detailed span on conflicting versions
#[derive(serde::Serialize, serde::Deserialize)]
struct InlineMetadata {
requires_python: String,
dependencies: Vec<String>,
}
let find_requirements = code.lines().find_position(|x| {
x.starts_with("#requirements:")
|| x.starts_with("# requirements:")
|| x.starts_with("# /// script")
});
if let Some((pos, item)) = find_requirements {
let mut requirements = HashMap::new();
if item.starts_with("# /// script") {
let mut incorrect = false;
let metadata = code
.lines()
.skip(pos + 1)
.map_while(|x| {
incorrect = !x.starts_with('#');
if incorrect || x.starts_with("# ///") {
None
} else {
x.get(1..)
}
})
.join("\n")
.parse::<toml::Table>()
.map_err(to_anyhow)?;
{
if let Some(v) = metadata.get("requires-python").and_then(|v| v.as_str()) {
push_version_specifiers(true, v.to_owned())?;
let mut check = |is_py_xyz, numeric| -> error::Result<()> {
if is_py_xyz {
if let Some(v) = annotated_pyv_numeric {
if *v != numeric {
return Err(error::Error::from(anyhow::anyhow!(
"Annotated 2 or more different python versions: \n - py{v} at {}\n - py{numeric} at {path}\nIt is possible to use only one.",
path_where_annotated_pyv.clone().unwrap_or("Unknown".to_owned())
)));
}
};
} else {
*annotated_pyv_numeric = Some(numeric);
}
*path_where_annotated_pyv = Some(path.to_owned());
}
Ok(())
};
metadata
.get("dependencies")
.and_then(|dependencies| dependencies.as_array())
.inspect(|list| {
for dependency_v in list.into_iter() {
let requirement = dependency_v.as_str().unwrap_or("ERROR").to_owned();
check(py310, 310)?;
check(py311, 311)?;
check(py312, 312)?;
check(py313, 313)?;
let find_requirements = code
.lines()
.find_position(|x| x.starts_with("#requirements:") || x.starts_with("# requirements:"));
if let Some((pos, _)) = find_requirements {
let mut requirements = HashMap::new();
code.lines()
.skip(pos + 1)
.map_while(|x| {
RE.captures(x).and_then(|x| {
x.get(1).map(|m| {
let requirement = m.as_str().to_string();
let key = extract_pkg_name(&requirement);
requirements.insert(
key.clone(),
@@ -403,31 +367,11 @@ async fn parse_python_imports_inner(
key,
},
);
}
});
} else {
code.lines()
.skip(pos + 1)
.map_while(|x| {
RE.captures(x).and_then(|x| {
x.get(1).map(|m| {
let requirement = m.as_str().to_string();
let key = extract_pkg_name(&requirement);
requirements.insert(
key.clone(),
NImportResolved::Pin {
pins: vec![ImportPin {
pkg: requirement.clone(),
path: Default::default(),
}],
key,
},
);
})
})
})
.collect_vec();
}
})
.collect_vec();
Ok(requirements)
} else {
let find_extra_requirements = code.lines().find_position(|x| {
@@ -498,7 +442,7 @@ async fn parse_python_imports_inner(
&rpath,
db,
already_visited,
version_specifiers,
annotated_pyv_numeric,
path_where_annotated_pyv,
)
.await?

View File

@@ -18,8 +18,16 @@ def main():
pass
";
let (r, ..) =
parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?;
let mut already_visited = vec![];
let (r, ..) = parse_python_imports(
code,
"test-workspace",
"f/foo/bar",
&db,
&mut already_visited,
&mut None,
)
.await?;
// println!("{}", serde_json::to_string(&r)?);
assert_eq!(
r,
@@ -51,8 +59,16 @@ def main():
pass
";
let (r, ..) =
parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?;
let mut already_visited = vec![];
let (r, ..) = parse_python_imports(
code,
"test-workspace",
"f/foo/bar",
&db,
&mut already_visited,
&mut None,
)
.await?;
println!("{}", serde_json::to_string(&r)?);
assert_eq!(r, vec!["burkina=0.4", "nigeria"]);
@@ -73,9 +89,17 @@ def main():
pass
";
let mut already_visited = vec![];
let (r, ..) =
parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?;
let (r, ..) = parse_python_imports(
code,
"test-workspace",
"f/foo/bar",
&db,
&mut already_visited,
&mut None,
)
.await?;
println!("{}", serde_json::to_string(&r)?);
assert_eq!(
r,

View File

@@ -83,21 +83,6 @@ pub fn parse_bigquery_sig(code: &str) -> anyhow::Result<MainArgSignature> {
}
}
pub fn parse_duckdb_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_duckdb_file(&code)?;
if let Some(args) = parsed {
Ok(MainArgSignature {
star_args: false,
star_kwargs: false,
args,
no_main_func: None,
has_preprocessor: None,
})
} else {
Err(anyhow!("Error parsing sql".to_string()))
}
}
pub fn parse_snowflake_sig(code: &str) -> anyhow::Result<MainArgSignature> {
let parsed = parse_snowflake_file(&code)?;
if let Some(x) = parsed {
@@ -227,9 +212,6 @@ lazy_static::lazy_static! {
// -- @name (type) = default
static ref RE_ARG_BIGQUERY: Regex = Regex::new(r#"(?m)^-- @(\w+) \((\w+(?:\[\])?)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
// -- $name (type) = default
static ref RE_ARG_DUCKDB: Regex = Regex::new(r#"(?m)^-- \$(\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
static ref RE_ARG_SNOWFLAKE: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
@@ -595,35 +577,6 @@ fn parse_bigquery_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
Ok(Some(args))
}
fn parse_duckdb_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
let mut args: Vec<Arg> = vec![];
for cap in RE_ARG_DUCKDB.captures_iter(code) {
let name = cap.get(1).map(|x| x.as_str().to_string()).unwrap();
let typ = cap
.get(2)
.map(|x| x.as_str().to_string().to_lowercase())
.unwrap();
let default = cap.get(3).map(|x| x.as_str().to_string());
let has_default = default.is_some();
let parsed_typ = parse_duckdb_typ(typ.as_str());
let parsed_default = default.and_then(|x| parsed_default(&parsed_typ, x));
args.push(Arg {
name,
typ: parsed_typ,
default: parsed_default,
otyp: Some(typ),
has_default,
oidx: None,
});
}
args.append(&mut parse_sql_sanitized_interpolation(code));
Ok(Some(args))
}
fn parse_snowflake_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
let mut args: Vec<Arg> = vec![];
@@ -776,33 +729,6 @@ pub fn parse_bigquery_typ(typ: &str) -> Typ {
}
}
pub fn parse_duckdb_typ(typ: &str) -> Typ {
if typ.ends_with("[]") {
let base_typ = parse_duckdb_typ(typ.strip_suffix("[]").unwrap());
Typ::List(Box::new(base_typ))
} else {
match typ {
"varchar" | "char" | "bpchar" | "text" | "string" => Typ::Str(None),
"blob" | "bytea" | "binary" | "varbinary" | "bitstring" => Typ::Bytes,
"boolean" | "bool" | "bit" | "logical" => Typ::Bool,
"bigint" | "int8" | "long" | "integer" | "int4" | "int" | "smallint" | "int2"
| "short" | "tinyint" | "int1" | "signed" | "ubigint" | "uhugeint" | "uinteger"
| "usmallint" | "utinyint" => Typ::Int,
"decimal" | "numeric" | "double" | "float8" | "float" | "float4" | "real" => Typ::Float,
"date"
| "time"
| "timestamp with time zone"
| "timestamptz"
| "timestamp"
| "datetime" => Typ::Datetime,
"uuid" | "json" => Typ::Str(None),
"interval" | "hugeint" => Typ::Str(None),
"s3object" => Typ::Resource("S3Object".to_string()),
_ => Typ::Str(None),
}
}
}
pub fn parse_snowflake_typ(typ: &str) -> Typ {
match typ {
"varchar" => Typ::Str(None),

View File

@@ -96,12 +96,6 @@ pub fn parse_oracledb(code: &str) -> String {
wrap_sig(windmill_parser_sql::parse_oracledb_sig(code))
}
#[cfg(feature = "sql-parser")]
#[wasm_bindgen]
pub fn parse_duckdb(code: &str) -> String {
wrap_sig(windmill_parser_sql::parse_duckdb_sig(code))
}
#[cfg(feature = "sql-parser")]
#[wasm_bindgen]
pub fn parse_bigquery(code: &str) -> String {

View File

@@ -1,13 +1,8 @@
#[cfg(feature = "private")]
#[allow(unused)]
pub use crate::ee::*;
#[cfg(not(feature = "private"))]
pub async fn set_license_key(_license_key: String) -> () {
// Implementation is not open source
}
#[cfg(all(feature = "enterprise", not(feature = "private")))]
#[cfg(feature = "enterprise")]
pub async fn verify_license_key() -> () {
// Implementation is not open source
}

View File

@@ -5,6 +5,7 @@
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
use anyhow::Context;
use monitor::{
load_base_url, load_otel, reload_critical_alerts_on_db_oversize,
@@ -22,15 +23,12 @@ use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
time::{Duration, Instant},
};
use strum::IntoEnumIterator;
use tokio::{fs::File, io::AsyncReadExt, task::JoinHandle};
use uuid::Uuid;
use windmill_api::HTTP_CLIENT;
#[cfg(feature = "enterprise")]
use windmill_common::ee_oss::{
maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID,
};
use windmill_common::ee::{maybe_renew_license_key_on_start, LICENSE_KEY_ID, LICENSE_KEY_VALID};
use windmill_common::{
agent_workers::build_agent_http_client,
@@ -51,12 +49,8 @@ use windmill_common::{
TIMEOUT_WAIT_RESULT_SETTING,
},
scripts::ScriptLang,
stats_oss::schedule_stats,
triggers::TriggerKind,
utils::{
create_default_worker_suffix, create_ssh_agent_worker_suffix, worker_name_with_suffix,
Mode, GIT_VERSION, HOSTNAME, MODE_AND_ADDONS,
},
stats_ee::schedule_stats,
utils::{hostname, rd_string, Mode, GIT_VERSION, MODE_AND_ADDONS},
worker::{
reload_custom_tags_setting, Connection, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP,
},
@@ -74,13 +68,14 @@ use tikv_jemallocator::Jemalloc;
static GLOBAL: Jemalloc = Jemalloc;
#[cfg(feature = "parquet")]
use windmill_common::global_settings::OBJECT_STORE_CONFIG_SETTING;
use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING;
use windmill_worker::{
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR,
DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR,
JAVA_CACHE_DIR, NU_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR,
PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, UV_CACHE_DIR,
PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, TAR_PY310_CACHE_DIR,
TAR_PY311_CACHE_DIR, TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR,
};
use crate::monitor::{
@@ -96,15 +91,13 @@ use crate::monitor::{
};
#[cfg(feature = "parquet")]
use windmill_common::s3_helpers::reload_object_store_setting;
use crate::monitor::reload_s3_cache_setting;
const DEFAULT_NUM_WORKERS: usize = 1;
const DEFAULT_PORT: u16 = 8000;
const DEFAULT_SERVER_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
#[cfg(feature = "private")]
pub mod ee;
mod ee_oss;
mod ee;
mod monitor;
pub fn setup_deno_runtime() -> anyhow::Result<()> {
@@ -159,7 +152,6 @@ lazy_static::lazy_static! {
.ok()
.and_then(|x| x.parse::<u64>().ok())
.unwrap_or(3600 * 12);
}
pub fn main() -> anyhow::Result<()> {
@@ -268,7 +260,7 @@ async fn windmill_main() -> anyhow::Result<()> {
tracing::error!("Failed to install rustls crypto provider");
}
let hostname = HOSTNAME.to_owned();
let hostname = hostname();
let mode_and_addons = MODE_AND_ADDONS.clone();
let mode = mode_and_addons.mode;
@@ -347,7 +339,7 @@ async fn windmill_main() -> anyhow::Result<()> {
"Creating http client for cluster using base internal url {}",
std::env::var("BASE_INTERNAL_URL").unwrap_or_default()
);
let suffix = create_ssh_agent_worker_suffix(&hostname);
let suffix = windmill_common::utils::worker_suffix(&hostname, &rd_string(5));
(
Connection::Http(build_agent_http_client(&suffix)),
Some(suffix),
@@ -559,7 +551,7 @@ Windmill Community Edition {GIT_VERSION}
_ = indexer_rx.recv() => {
tracing::info!("Received killpill, aborting index initialization");
},
res = windmill_indexer::completed_runs_oss::init_index(&db) => {
res = windmill_indexer::completed_runs_ee::init_index(&db) => {
let res = res?;
reader = Some(res.0);
writer = Some(res.1);
@@ -581,7 +573,7 @@ Windmill Community Edition {GIT_VERSION}
async {
if let Some(db) = conn.as_sql() {
if let Some(index_writer) = index_writer2 {
windmill_indexer::completed_runs_oss::run_indexer(
windmill_indexer::completed_runs_ee::run_indexer(
db.clone(),
index_writer,
indexer_rx,
@@ -603,7 +595,7 @@ Windmill Community Edition {GIT_VERSION}
_ = indexer_rx.recv() => {
tracing::info!("Received killpill, aborting index initialization");
},
res = windmill_indexer::service_logs_oss::init_index(&db, killpill_tx.clone()) => {
res = windmill_indexer::service_logs_ee::init_index(&db, killpill_tx.clone()) => {
let res = res?;
reader = Some(res.0);
writer = Some(res.1);
@@ -625,7 +617,7 @@ Windmill Community Edition {GIT_VERSION}
async {
if let Some(db) = conn.as_sql() {
if let Some(log_index_writer) = log_index_writer2 {
windmill_indexer::service_logs_oss::run_indexer(
windmill_indexer::service_logs_ee::run_indexer(
db.clone(),
log_index_writer,
log_indexer_rx,
@@ -682,21 +674,19 @@ Windmill Community Edition {GIT_VERSION}
let base_internal_url = base_internal_rx.await?;
if worker_mode {
let mut workers = vec![];
for i in 0..num_workers {
let suffix = if i == 0 && first_suffix.is_some() {
let suffix: String = if i == 0 && first_suffix.as_ref().is_some() {
first_suffix.as_ref().unwrap().clone()
} else {
create_default_worker_suffix(&hostname)
windmill_common::utils::worker_suffix(&hostname, &rd_string(5))
};
let worker_conn = WorkerConn {
conn: if i == 0 || mode != Mode::Agent {
conn.clone()
} else {
Connection::Http(build_agent_http_client(&suffix))
},
worker_name: worker_name_with_suffix(
worker_name: windmill_common::utils::worker_name_with_suffix(
mode == Mode::Agent,
WORKER_GROUP.as_str(),
&suffix,
@@ -801,37 +791,11 @@ Windmill Community Edition {GIT_VERSION}
let payload = n.payload();
tracing::info!("Runnable version change detected: {}", payload);
match payload.split(':').collect::<Vec<&str>>().as_slice() {
[workspace_id, source_type, path, kind] => {
[workspace_id, source_type, path] => {
let key = (workspace_id.to_string(), path.to_string());
match source_type {
&"script" => {
windmill_common::DEPLOYED_SCRIPT_HASH_CACHE.remove(&key);
match kind {
&"preprocessor" => {
match sqlx::query_scalar!(
"SELECT fv.id
FROM flow f
INNER JOIN flow_version fv ON fv.id = f.versions[array_upper(f.versions, 1)]
WHERE fv.value->'preprocessor_module'->'value'->>'path' = $1 AND f.workspace_id = $2",
path,
workspace_id
).fetch_all(&db).await {
Ok(flow_versions) => {
tracing::debug!("Workspace preprocessor {} changed, removing runnable format version cache for flow versions {:?}", path, flow_versions);
for version in flow_versions {
for trigger_kind in TriggerKind::iter() {
let key = (windmill_common::triggers::HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()), version, trigger_kind);
windmill_common::triggers::RUNNABLE_FORMAT_VERSION_CACHE.remove(&key);
}
}
}
Err(e) => {
tracing::error!("Error fetching flow paths: {e:#}");
}
}
},
_ => {}
}
}
&"flow" => {
windmill_common::FLOW_VERSION_CACHE.remove(&key);
@@ -861,11 +825,6 @@ Windmill Community Edition {GIT_VERSION}
}
};
},
"notify_token_invalidation" => {
let token = n.payload();
tracing::info!("Token invalidation detected for token: {}...", &token[..token.len().min(8)]);
windmill_api::auth::invalidate_token_from_cache(token);
},
"notify_global_setting_change" => {
tracing::info!("Global setting change detected: {}", n.payload());
match n.payload() {
@@ -898,7 +857,7 @@ Windmill Community Edition {GIT_VERSION}
if let Err(e) = load_tag_per_workspace_workspaces(&db).await {
tracing::error!("Error loading default tag per workspace workspaces: {e:#}");
}
},
}
SMTP_SETTING => {
reload_smtp_config(&db).await;
},
@@ -921,9 +880,9 @@ Windmill Community Edition {GIT_VERSION}
reload_job_default_timeout_setting(&conn).await
},
#[cfg(feature = "parquet")]
OBJECT_STORE_CONFIG_SETTING => {
OBJECT_STORE_CACHE_CONFIG_SETTING => {
if !disable_s3_store {
reload_object_store_setting(&db).await;
reload_s3_cache_setting(&db).await
}
},
SCIM_TOKEN_SETTING => {
@@ -1015,6 +974,7 @@ Windmill Community Edition {GIT_VERSION}
tracing::error!(error = %e, "Could not reload critical alert UI setting");
}
},
a @_ => {
tracing::info!("Unrecognized Global Setting Change Payload: {:?}", a);
}
@@ -1066,9 +1026,7 @@ Windmill Community Edition {GIT_VERSION}
}
if server_mode {
if !*windmill_common::QUIET_LOGS {
tracing::info!("monitor task started");
}
tracing::info!("monitor task started");
}
monitor_db(
&conn,
@@ -1080,9 +1038,7 @@ Windmill Community Edition {GIT_VERSION}
)
.await;
if server_mode {
if !*windmill_common::QUIET_LOGS {
tracing::info!("monitor task finished");
}
tracing::info!("monitor task finished");
}
},
}
@@ -1103,7 +1059,7 @@ Windmill Community Edition {GIT_VERSION}
tracing::info!("Reloading config after 12 hours");
initial_load(&conn, tx.clone(), worker_mode, server_mode, #[cfg(feature = "parquet")] disable_s3_store).await;
#[cfg(feature = "enterprise")]
ee_oss::verify_license_key().await;
ee::verify_license_key().await;
}
}
},
@@ -1191,7 +1147,6 @@ async fn listen_pg(url: &str) -> Option<PgListener> {
"notify_webhook_change",
"notify_workspace_envs_change",
"notify_runnable_version_change",
"notify_token_invalidation",
];
#[cfg(feature = "http_trigger")]
@@ -1285,6 +1240,10 @@ pub async fn run_workers(
PY311_CACHE_DIR,
PY312_CACHE_DIR,
PY313_CACHE_DIR,
TAR_PY310_CACHE_DIR,
TAR_PY311_CACHE_DIR,
TAR_PY312_CACHE_DIR,
TAR_PY313_CACHE_DIR,
BUN_BUNDLE_CACHE_DIR,
GO_CACHE_DIR,
GO_BIN_CACHE_DIR,

View File

@@ -29,19 +29,16 @@ use windmill_api::{
};
#[cfg(feature = "enterprise")]
use windmill_common::ee_oss::low_disk_alerts;
use windmill_common::ee::low_disk_alerts;
#[cfg(feature = "enterprise")]
use windmill_common::ee_oss::{jobs_waiting_alerts, worker_groups_alerts};
use windmill_common::ee::{jobs_waiting_alerts, worker_groups_alerts};
use windmill_common::client::AuthedClient;
#[cfg(feature = "oauth2")]
use windmill_common::global_settings::OAUTH_SETTING;
#[cfg(feature = "parquet")]
use windmill_common::s3_helpers::reload_object_store_setting;
use windmill_common::{
agent_workers::DECODED_AGENT_TOKEN,
auth::create_token_for_owner,
ee_oss::CriticalErrorChannel,
ee::CriticalErrorChannel,
error,
flow_status::{FlowStatus, FlowStatusModule},
global_settings::{
@@ -78,18 +75,24 @@ use windmill_common::{
};
use windmill_queue::{cancel_job, MiniPulledJob, SameWorkerPayload};
use windmill_worker::{
handle_job_error, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
handle_job_error, AuthedClient, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES,
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN,
NPM_CONFIG_REGISTRY, NUGET_CONFIG, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
};
#[cfg(feature = "parquet")]
use windmill_common::s3_helpers::ObjectStoreReload;
use windmill_common::s3_helpers::{
build_object_store_from_settings, build_s3_client_from_settings, S3Settings,
OBJECT_STORE_CACHE_SETTINGS,
};
#[cfg(feature = "parquet")]
use windmill_common::global_settings::OBJECT_STORE_CACHE_CONFIG_SETTING;
#[cfg(feature = "enterprise")]
use crate::ee_oss::verify_license_key;
use crate::ee::verify_license_key;
use crate::ee_oss::set_license_key;
use crate::ee::set_license_key;
#[cfg(feature = "prometheus")]
lazy_static::lazy_static! {
@@ -238,23 +241,7 @@ pub async fn initial_load(
#[cfg(feature = "parquet")]
if !disable_s3_store {
if let Some(db) = conn.as_sql() {
let db2 = db.clone();
match reload_object_store_setting(db).await {
ObjectStoreReload::Later => {
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(10)).await;
match reload_object_store_setting(&db2).await {
ObjectStoreReload::Later => {
tracing::error!("Giving up on loading object store setting");
}
ObjectStoreReload::Never => {
tracing::info!("Object store setting successfully loaded");
}
}
});
}
ObjectStoreReload::Never => (),
}
reload_s3_cache_setting(db).await;
}
}
@@ -644,7 +631,7 @@ async fn send_log_file_to_object_store(
}
#[cfg(feature = "parquet")]
let s3_client = windmill_common::s3_helpers::get_object_store().await;
let s3_client = OBJECT_STORE_CACHE_SETTINGS.read().await.clone();
#[cfg(feature = "parquet")]
if let Some(s3_client) = s3_client {
let path = std::path::Path::new(TMP_WINDMILL_LOGS_SERVICE)
@@ -930,7 +917,10 @@ async fn delete_log_files_from_disk_and_store(
_s3_prefix: &str,
) {
#[cfg(feature = "parquet")]
let os = windmill_common::s3_helpers::get_object_store().await;
let os = windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS
.read()
.await
.clone();
#[cfg(not(feature = "parquet"))]
let os: Option<()> = None;
@@ -1111,6 +1101,64 @@ pub async fn reload_delete_logs_periodically_setting(conn: &Connection) {
}
}
#[cfg(feature = "parquet")]
pub async fn reload_s3_cache_setting(db: &DB) {
use windmill_common::{
ee::{get_license_plan, LicensePlan},
s3_helpers::ObjectSettings,
};
let s3_config = load_value_from_global_settings(db, OBJECT_STORE_CACHE_CONFIG_SETTING).await;
if let Err(e) = s3_config {
tracing::error!("Error reloading s3 cache config: {:?}", e)
} else {
if let Some(v) = s3_config.unwrap() {
if matches!(get_license_plan().await, LicensePlan::Pro) {
tracing::error!("S3 cache is not available for pro plan");
return;
}
let mut s3_cache_settings = OBJECT_STORE_CACHE_SETTINGS.write().await;
let setting = serde_json::from_value::<ObjectSettings>(v);
if let Err(e) = setting {
tracing::error!("Error parsing s3 cache config: {:?}", e)
} else {
let setting = setting.unwrap();
let bucket = setting.get_bucket().map(|b| b.to_string());
let s3_client = build_object_store_from_settings(setting).await;
if let Err(e) = s3_client {
tracing::error!("Error building s3 client from settings: {:?}", e)
} else {
tracing::info!("Loaded object store {:?}", bucket);
*s3_cache_settings = Some(s3_client.unwrap());
}
}
} else {
let mut s3_cache_settings = OBJECT_STORE_CACHE_SETTINGS.write().await;
if std::env::var("S3_CACHE_BUCKET").is_ok() {
if matches!(get_license_plan().await, LicensePlan::Pro) {
tracing::error!("S3 cache is not available for pro plan");
return;
}
*s3_cache_settings = build_s3_client_from_settings(S3Settings {
bucket: None,
region: None,
access_key: None,
secret_key: None,
endpoint: None,
store_logs: None,
path_style: None,
allow_http: None,
port: None,
})
.await
.ok();
} else {
*s3_cache_settings = None;
}
}
}
}
pub async fn reload_job_default_timeout_setting(conn: &Connection) {
reload_option_setting_with_tracing(
conn,
@@ -1324,6 +1372,7 @@ pub async fn monitor_db(
initial_load: bool,
_killpill_tx: KillpillSender,
) {
tracing::info!("Starting periodic monitor task");
let zombie_jobs_f = async {
if server_mode && !initial_load && !*DISABLE_ZOMBIE_JOBS_MONITORING {
if let Some(db) = conn.as_sql() {
@@ -1421,6 +1470,7 @@ pub async fn monitor_db(
apply_autoscaling_f,
update_min_worker_version_f,
);
tracing::info!("Periodic monitor task completed");
}
pub async fn expose_queue_metrics(db: &Pool<Postgres>) {
@@ -1607,7 +1657,7 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
if let Some(q) = q_oauth {
if let Ok(v) = serde_json::from_value::<
Option<HashMap<String, windmill_api::oauth2_oss::OAuthClient>>,
Option<HashMap<String, windmill_api::oauth2_ee::OAuthClient>>,
>(q.clone())
{
v
@@ -1628,7 +1678,7 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
{
if let Some(db) = conn.as_sql() {
let mut l = windmill_api::OAUTH_CLIENTS.write().await;
*l = windmill_api::oauth2_oss::build_oauth_clients(&base_url, oauths, db).await
*l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths, db).await
.map_err(|e| tracing::error!("Error building oauth clients (is the oauth.json mounted and in correct format? Use '{}' as minimal oauth.json): {}", "{}", e))
.unwrap();
}
@@ -1912,12 +1962,12 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
.await
.expect("could not create job token");
let client = AuthedClient::new(
base_internal_url.to_string(),
job.workspace_id.to_string(),
let client = AuthedClient {
base_internal_url: base_internal_url.to_string(),
token,
None,
);
workspace: job.workspace_id.to_string(),
force_client: None,
};
let last_ping = job.last_ping.clone();
let error_message = format!(
@@ -1936,7 +1986,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, base_internal_url: &str, worker
None,
error::Error::ExecutionErr(error_message),
true,
Some(&same_worker_tx_never_used),
same_worker_tx_never_used,
"",
worker_name,
send_result_never_used,

View File

@@ -4,8 +4,8 @@ script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root_dirpath="$(cd "${script_dirpath}/.." && pwd)"
REVERT="NO"
REVERT_PREVIOUS="NO"
COPY="NO"
MOVE_NEW_FILES="NO"
EE_CODE_DIR="../windmill-ee-private/"
while [[ $# -gt 0 ]]; do
@@ -16,7 +16,13 @@ while [[ $# -gt 0 ]]; do
# this to work (commit hooks should prevent this from happening, as well as the fact
# that we're using symlinks by default).
REVERT="YES"
MOVE_NEW_FILES="YES"
shift
;;
--revert-previous)
# This is a special case of --revert that will revert to the previous commit.
REVERT="YES"
REVERT_PREVIOUS="YES"
echo "Reverting to previous commit"
shift
;;
-c|--copy)
@@ -27,11 +33,6 @@ while [[ $# -gt 0 ]]; do
COPY="YES"
shift # past argument
;;
-m|--move-new-files)
# This moves all new EE files from the public repository to the private repository.
MOVE_NEW_FILES="YES"
shift # past argument
;;
-d|--dir)
# Path to the local directory of the windmill-ee-private repository. By defaults, it
# assumes it is cloned next to the Windmill OSS repo.
@@ -69,34 +70,29 @@ if [ "$REVERT" == "YES" ]; then
for ee_file in $(find ${EE_CODE_DIR} -name "*ee.rs"); do
ce_file="${ee_file/${EE_CODE_DIR}/}"
ce_file="${root_dirpath}/backend/${ce_file}"
rm ${ce_file} || true
if [ "$REVERT_PREVIOUS" == "YES" ]; then
git checkout HEAD@{3} ${ce_file} || true
else
git restore --staged ${ce_file} || true
git restore ${ce_file} || true
fi
done
elif [ "$MOVE_NEW_FILES" == "NO" ]; then
else
# This replaces all files in current repo with alternative EE files in windmill-ee-private
for ee_file in $(find "${EE_CODE_DIR}" -name "*ee.rs"); do
ce_file="${ee_file/${EE_CODE_DIR}/}"
ce_file="${root_dirpath}/backend/${ce_file}"
if [ "$COPY" == "YES" ]; then
cp "${ee_file}" "${ce_file}"
echo "File copied '${ee_file}' -->> '${ce_file}'"
ce_file="${ee_file/${EE_CODE_DIR}/}"
ce_file="${root_dirpath}/backend/${ce_file}"
if [[ -f "${ce_file}" ]]; then
rm "${ce_file}"
if [ "$COPY" == "YES" ]; then
cp "${ee_file}" "${ce_file}"
echo "File copied '${ee_file}' -->> '${ce_file}'"
else
ln -s "${ee_file}" "${ce_file}"
echo "Symlink created '${ee_file}' -->> '${ce_file}'"
fi
else
ln -s "${ee_file}" "${ce_file}" || true
echo "Symlink created '${ee_file}' -->> '${ce_file}'"
echo "File ${ce_file} is not a file, ignoring"
fi
done
fi
if [ "$MOVE_NEW_FILES" == "YES" ]; then
for ce_file in $(find "${root_dirpath}"/backend/windmill-*/src/ -name "*ee.rs"); do
backend_dirpath="${root_dirpath}/backend/"
ee_file="${ce_file/${backend_dirpath}/}"
ee_file="${EE_CODE_DIR}${ee_file}"
if [ ! -f "${ee_file}" ]; then
mv "${ce_file}" "${ee_file}"
if [ ! "$REVERT" == "YES" ]; then
ln -s "${ee_file}" "${ce_file}"
fi
echo "File moved '${ce_file}' -->> '${ee_file}'"
fi
done
fi

View File

@@ -1,20 +0,0 @@
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
'test-workspace',
'test-user',
'# py312
',
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
'',
'',
'f/multipython/aliases', 2468135790, 'python3', '');
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
'test-workspace',
'test-user',
'# py: >=3.9,!=3.12.2
',
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
'',
'',
'f/multipython/script1', 2345678901, 'python3', '');

View File

@@ -3969,7 +3969,8 @@ async fn assert_lockfile(
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_requirements_python(db: Pool<Postgres>) {
let content = r#"# py: ==3.11.11
let content = r#"
# py311
# requirements:
# tiny==0.1.3
@@ -3987,7 +3988,7 @@ def main():
&db,
content,
ScriptLang::Python3,
vec!["# py: 3.11.11", "tiny==0.1.3"],
vec!["# py311", "tiny==0.1.3"],
)
.await;
}
@@ -3996,7 +3997,8 @@ def main():
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_extra_requirements_python(db: Pool<Postgres>) {
{
let content = r#"# py: ==3.11.11
let content = r#"
# py311
# extra_requirements:
# tiny
@@ -4014,7 +4016,7 @@ def main():
&db,
content,
ScriptLang::Python3,
vec!["# py: 3.11.11", "bottle==0.13.2", "tiny==0.1.2"],
vec!["# py311", "bottle==0.13.2", "tiny==0.1.2"],
)
.await;
}
@@ -4023,7 +4025,8 @@ def main():
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_extra_requirements_python2(db: Pool<Postgres>) {
let content = r#"# py: ==3.11.11
let content = r#"
# py311
# extra_requirements:
# tiny==0.1.3
@@ -4037,7 +4040,7 @@ def main():
&db,
content,
ScriptLang::Python3,
vec!["# py: 3.11.11", "simplejson==3.20.1", "tiny==0.1.3"],
vec!["# py311", "simplejson==3.20.1", "tiny==0.1.3"],
)
.await;
}
@@ -4045,7 +4048,8 @@ def main():
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "lockfile_python"))]
async fn test_pins_python(db: Pool<Postgres>) {
let content = r#"# py: ==3.11.11
let content = r#"
# py311
# extra_requirements:
# tiny==0.1.3
# bottle==0.13.2
@@ -4065,7 +4069,7 @@ def main():
content,
ScriptLang::Python3,
vec![
"# py: 3.11.11",
"# py311",
"bottle==0.13.2",
"microdot==2.2.0",
"simplejson==3.19.3",
@@ -4074,39 +4078,6 @@ def main():
)
.await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "multipython"))]
async fn test_multipython_python(db: Pool<Postgres>) {
let content = r#"# py: <=3.12.2, >=3.12.0
import f.multipython.script1
import f.multipython.aliases
"#
.to_string();
assert_lockfile(&db, content, ScriptLang::Python3, vec!["# py: 3.12.1\n"]).await;
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "multipython"))]
async fn test_inline_script_metadata_python(db: Pool<Postgres>) {
let content = r#"# py_select_latest
# /// script
# requires-python = ">3.11,<3.12.3,!=3.12.2"
# dependencies = [
# "tiny==0.1.3",
# ]
# ///
"#
.to_string();
assert_lockfile(
&db,
content,
ScriptLang::Python3,
vec!["# py: 3.12.1", "tiny==0.1.3"],
)
.await;
}
#[sqlx::test(fixtures("base", "result_format"))]
async fn test_result_format(db: Pool<Postgres>) {
let ordered_result_job_id = "1eecb96a-c8b0-4a3d-b1b6-087878c55e41";

View File

@@ -10,6 +10,7 @@ if [[ "$(uname)" == "Darwin" ]]; then
fi
cargo sqlx prepare --workspace -- --all-targets --all-features
./substitute_ee_code.sh -r --dir ../windmill-ee-private
# Undo the samael changes on macOS
if [[ "$(uname)" == "Darwin" ]]; then

View File

@@ -10,7 +10,6 @@ path = "src/lib.rs"
[features]
default = []
private = ["windmill-audit/private"]
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker/enterprise"]
stripe = []
agent_worker_server = []
@@ -19,7 +18,7 @@ benchmark = []
embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core", "dep:candle-transformers", "dep:candle-nn"]
parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/parquet", "windmill-worker/parquet"]
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus", "windmill-worker/prometheus"]
openidconnect = ["dep:openidconnect", "windmill-common/openidconnect"]
openidconnect = ["dep:openidconnect"]
tantivy = ["dep:windmill-indexer"]
kafka = ["dep:rdkafka"]
nats = ["dep:async-nats", "dep:nkeys"]
@@ -37,10 +36,9 @@ deno_core = ["dep:deno_core", "dep:deno_error"]
gcp_trigger = ["dep:thiserror", "dep:google-cloud-pubsub", "dep:google-cloud-googleapis", "dep:tonic"]
cloud = ["windmill-common/cloud"]
mcp = ["dep:rmcp"]
python = []
[dependencies]
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true }
rmcp = { git = "https://github.com/windmill-labs/rust-sdk", features = ["transport-sse-server"], optional = true }
windmill-queue.workspace = true
windmill-common = { workspace = true, default-features = false }
windmill-audit.workspace = true

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.496.3
version: 1.491.5
title: Windmill API
contact:
@@ -377,9 +377,6 @@ paths:
type: string
company:
type: string
skip_email:
type: boolean
description: Skip sending email notifications to the user
required:
- email
- password
@@ -4501,13 +4498,6 @@ paths:
in: query
schema:
type: boolean
- name: languages
in: query
description: |
Filter to only include scripts written in the given languages.
Accepts multiple values as a comma-separated list.
schema:
type: string
responses:
"200":
description: All scripts
@@ -4903,6 +4893,8 @@ paths:
description: Script version/hash
content:
application/json:
required: false
schema:
$ref: "#/components/schemas/ScriptHistory"
@@ -5464,6 +5456,8 @@ paths:
description: Flow version
content:
application/json:
required: false
schema:
$ref: "#/components/schemas/FlowVersion"
@@ -5493,7 +5487,8 @@ paths:
operationId: getFlowVersion
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: version
- type: string
name: version
in: path
required: true
schema:
@@ -5515,7 +5510,8 @@ paths:
operationId: updateFlowHistory
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: version
- type: string
name: version
in: path
required: true
schema:
@@ -6143,8 +6139,10 @@ paths:
description: App version
content:
application/json:
required: false
schema:
$ref: "#/components/schemas/AppHistory"
/w/{workspace}/apps/list_paths_from_workspace_runnable/{runnable_kind}/{path}:
get:
summary: list app paths from workspace runnable
@@ -6733,6 +6731,11 @@ paths:
responses:
"201":
description: stream of created job uuids separated by \n. Lines may start with 'Error:'
example: |
a1a74c0d-708e-4539-9768-e8b3d37996bd
f0949132-5b30-48fe-bac8-873f047df810
Error: Could not re-run 0b885808-ae89-4458-af95-c1ca3a13b0a5
52b9c01d-1125-4bbb-8bee-d41f26b70066
content:
text/event-stream:
schema:
@@ -7589,8 +7592,7 @@ paths:
description: job log
content:
text/plain:
schema:
type: string
type: string
/w/{workspace}/jobs_u/get_flow_debug_info/{id}:
get:
@@ -8451,31 +8453,6 @@ paths:
"201":
description: default error handler set
/w/{workspace}/http_triggers/create_many:
post:
summary: create many HTTP triggers
operationId: createHttpTriggers
tags:
- http_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: new http trigger
required: true
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/NewHttpTrigger"
responses:
"201":
description: http trigger created
content:
text/plain:
schema:
type: string
/w/{workspace}/http_triggers/create:
post:
summary: create http trigger
@@ -8627,7 +8604,8 @@ paths:
route_path:
type: string
http_method:
$ref: "#/components/schemas/HttpMethod"
type: string
enum: ["get", "post", "put", "delete", "patch"]
trigger_path:
type: string
workspaced_route:
@@ -9852,23 +9830,6 @@ paths:
items:
type: string
/w/{workspace}/postgres_triggers/postgres/version/{path}:
get:
summary: get postgres version
operationId: getPostgresVersion
tags:
- postgres_trigger
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: postgres version
content:
application/json:
schema:
type: string
/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}:
get:
summary: check if postgres configuration is set to logical
@@ -11144,23 +11105,6 @@ paths:
items:
$ref: "#/components/schemas/AutoscalingEvent"
/configs/list_available_python_versions:
get:
summary: Get currently available python versions provided by UV.
operationId: listAvailablePythonVersions
tags:
- config
# parameters:
responses:
"200":
description: List of python versions
content:
application/json:
schema:
type: array
items:
type: string
/agent_workers/create_agent_token:
post:
summary: create agent token
@@ -12508,6 +12452,7 @@ paths:
text/plain:
schema:
type: string
/concurrency_groups/list:
get:
summary: List all concurrency groups
@@ -12645,11 +12590,6 @@ paths:
required: true
schema:
type: string
- name: pagination_offset
in: query
required: false
schema:
type: integer
responses:
"200":
description: search results
@@ -12662,26 +12602,15 @@ paths:
description: a list of the terms that couldn't be parsed (and thus ignored)
type: array
items:
type: string
type: object
properties:
dancer:
type: string
hits:
description: the jobs that matched the query
type: array
items:
$ref: "#/components/schemas/JobSearchHit"
hit_count:
description: how many jobs matched in total
type: number
index_metadata:
description: Metadata about the index current state
type: object
properties:
indexed_until:
description: Datetime of the most recently indexed job
type: string
format: date-time
lost_lock_ownership:
description: Is the current indexer service being replaced
type: boolean
/srch/index/search/service_logs:
get:
@@ -13213,51 +13142,7 @@ components:
enum: [script, flow]
schemas:
# NOTE: Not so many generators and validators support this format:
# $ref: "../../openflow.openapi.yaml#/components/schemas"
# This is why it is better to inline each of schemas for better compat
# Do not change next line. It is used by python-client for pre-processing
# -- INLINE START --
OpenFlow:
$ref: "../../openflow.openapi.yaml#/components/schemas/OpenFlow"
FlowValue:
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowValue"
Retry:
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
StopAfterIf:
$ref: "../../openflow.openapi.yaml#/components/schemas/StopAfterIf"
FlowModule:
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowModule"
InputTransform:
$ref: "../../openflow.openapi.yaml#/components/schemas/InputTransform"
StaticTransform:
$ref: "../../openflow.openapi.yaml#/components/schemas/StaticTransform"
JavascriptTransform:
$ref: "../../openflow.openapi.yaml#/components/schemas/JavascriptTransform"
FlowModuleValue:
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowModuleValue"
RawScript:
$ref: "../../openflow.openapi.yaml#/components/schemas/RawScript"
PathScript:
$ref: "../../openflow.openapi.yaml#/components/schemas/PathScript"
PathFlow:
$ref: "../../openflow.openapi.yaml#/components/schemas/PathFlow"
ForloopFlow:
$ref: "../../openflow.openapi.yaml#/components/schemas/ForloopFlow"
WhileloopFlow:
$ref: "../../openflow.openapi.yaml#/components/schemas/WhileloopFlow"
BranchOne:
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchOne"
BranchAll:
$ref: "../../openflow.openapi.yaml#/components/schemas/BranchAll"
Identity:
$ref: "../../openflow.openapi.yaml#/components/schemas/Identity"
FlowStatus:
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatus"
FlowStatusModule:
$ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatusModule"
# -- INLINE END --
# Do not change line above
$ref: "../../openflow.openapi.yaml#/components/schemas"
AIProvider:
type: string
@@ -14324,8 +14209,7 @@ components:
ansible,
csharp,
nu,
java,
duckdb
java
# for related places search: ADD_NEW_LANG
]
@@ -14754,15 +14638,6 @@ components:
- custom_script
- signature
HttpMethod:
type: string
enum:
- get
- post
- put
- delete
- patch
HttpTrigger:
allOf:
- $ref: "#/components/schemas/TriggerExtraProperty"
@@ -14782,7 +14657,13 @@ components:
required:
- s3
http_method:
$ref: "#/components/schemas/HttpMethod"
type: string
enum:
- get
- post
- put
- delete
- patch
authentication_resource_path:
type: string
is_async:
@@ -14833,7 +14714,13 @@ components:
is_flow:
type: boolean
http_method:
$ref: "#/components/schemas/HttpMethod"
type: string
enum:
- get
- post
- put
- delete
- patch
authentication_resource_path:
type: string
is_async:
@@ -14884,7 +14771,13 @@ components:
is_flow:
type: boolean
http_method:
$ref: "#/components/schemas/HttpMethod"
type: string
enum:
- get
- post
- put
- delete
- patch
is_async:
type: boolean
authentication_method:
@@ -16298,8 +16191,10 @@ components:
- access_token
HubScriptKind:
type: string
enum: [script, failure, trigger, approval]
name: kind
schema:
type: string
enum: [script, failure, trigger, approval]
PolarsClientKwargs:
type: object
@@ -16891,6 +16786,7 @@ components:
type: string
required:
- s3
TeamsChannel:
type: object
required:
@@ -16914,4 +16810,4 @@ components:
channel_name:
type: string
description: Microsoft Teams channel name
minLength: 1
minLength: 1

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