mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 00:02:13 +00:00
* test: assert the unpacked repo symlink without following it `unpack_keeps_a_link_that_stays_in_the_repo` read through the link it had just unpacked. Windows stores a symlink's target verbatim and its object manager rejects the `/` in a POSIX one, so `read_to_string` came back with `ERROR_INVALID_NAME` and the release's `cargo_test_windows` job was red. Pin what the function is responsible for on every platform — the link is kept and materialized — and read through it only where a POSIX relative target resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx * test: key the cli sync-map fixtures with the platform separator A sync map is keyed with the platform separator on both sides — `FSFSElement` walks the tree with `path.join`, and the remote `ZipFSElement` starts at `"." + SEP` and joins from there — while an `!inline` reference is always forward-slash. `lock_dedup.ts` follows that convention; the fixtures did not, so on Windows they built a map shape the CLI never produces and 12 of them failed. `getTypeStrFromPath` is the same story: it matches `"dependencies" + SEP`, and the test handed it a forward-slashed path. Build the fixture keys through the separator, leaving the `!inline` references and the `present` map forward-slash, as `sync.ts` hands them over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx * ci: skip the discord comment relay when the thread lookup returns none A rate-limited or unauthorized Discord response carries no thread list, and under `bash -e` that aborted the step — jq cannot iterate null, nor parse the HTML error page Cloudflare answers a 429 with — before it reached the "thread not found, skipping" branch right below. Three comment relays failed that way on the 1.794.0 head. Keep the step green for both, but tell them apart: a response with no thread list is a delivery that was dropped for a reason worth seeing, so it warns with the body it got, while a PR that genuinely has no thread stays quiet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
223 lines
8.7 KiB
YAML
223 lines
8.7 KiB
YAML
name: "Notify Discord when a PR is opened or merged"
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
PR_TITLE:
|
|
description: "The title of the PR"
|
|
type: string
|
|
PR_URL:
|
|
description: "The URL of the PR"
|
|
type: string
|
|
PR_AUTHOR:
|
|
description: "The author of the PR"
|
|
type: string
|
|
PR_STATUS:
|
|
description: "The status of the PR"
|
|
type: string
|
|
DISCORD_CHANNEL_ID:
|
|
description: "The Discord channel ID"
|
|
type: string
|
|
PR_NUMBER:
|
|
description: "The number of the PR"
|
|
type: string
|
|
DISCORD_GUILD_ID:
|
|
description: "The Discord guild ID"
|
|
type: string
|
|
COMMENT_BODY:
|
|
description: "The comment body"
|
|
type: string
|
|
default: ""
|
|
COMMENT_AUTHOR:
|
|
description: "The comment author"
|
|
type: string
|
|
default: ""
|
|
COMMENT_URL:
|
|
description: "The comment URL"
|
|
type: string
|
|
default: ""
|
|
COMMENT_IS_EDIT:
|
|
description: "Whether this is an edit of an existing comment"
|
|
type: string
|
|
default: "false"
|
|
secrets:
|
|
DISCORD_WEBHOOK_URL:
|
|
description: "Discord Webhook URL"
|
|
required: false
|
|
DISCORD_BOT_TOKEN:
|
|
description: "Discord Bot Token"
|
|
|
|
jobs:
|
|
open_thread:
|
|
runs-on: ubicloud-standard-2
|
|
if: ${{ inputs.PR_STATUS == 'opened' }}
|
|
steps:
|
|
- 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
|
|
|
|
merge_success_emoji:
|
|
runs-on: ubuntu-latest
|
|
if: ${{ inputs.PR_STATUS == 'merged' }}
|
|
steps:
|
|
- name: React
|
|
env:
|
|
BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
|
|
CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }}
|
|
GUILD_ID: ${{ inputs.DISCORD_GUILD_ID }}
|
|
PR_NUMBER: ${{ inputs.PR_NUMBER }}
|
|
run: |
|
|
# 1) get PR thread
|
|
threads=$(curl -H "Authorization: Bot $BOT_TOKEN" "https://discord.com/api/v10/guilds/${GUILD_ID}/threads/active")
|
|
thread_id=$(
|
|
echo "$threads" \
|
|
| jq -r --arg cid "$CHANNEL_ID" \
|
|
--arg pref "#${PR_NUMBER}:" \
|
|
'.threads[]
|
|
| select(.parent_id == $cid and (.name | startswith($pref)))
|
|
| .id'
|
|
)
|
|
if [ -z "$thread_id" ]; then
|
|
echo "Thread not found"
|
|
exit 1
|
|
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")
|
|
message_id=$(echo "$messages" | jq -r '.[-1].id')
|
|
|
|
if [ -z "$message_id" ]; then
|
|
echo "Message not found"
|
|
exit 1
|
|
fi
|
|
|
|
# 3) add the ✅ reaction
|
|
curl -X PUT \
|
|
-H "Authorization: Bot $BOT_TOKEN" \
|
|
"https://discord.com/api/v10/channels/$thread_id/messages/$message_id/reactions/%E2%9C%85/@me"
|
|
|
|
post_comment:
|
|
runs-on: ubuntu-latest
|
|
if: ${{ inputs.PR_STATUS == 'comment' }}
|
|
steps:
|
|
- name: Post or update comment in Discord thread
|
|
env:
|
|
BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
|
|
CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }}
|
|
GUILD_ID: ${{ inputs.DISCORD_GUILD_ID }}
|
|
PR_NUMBER: ${{ inputs.PR_NUMBER }}
|
|
COMMENT_BODY: ${{ inputs.COMMENT_BODY }}
|
|
COMMENT_AUTHOR: ${{ inputs.COMMENT_AUTHOR }}
|
|
COMMENT_URL: ${{ inputs.COMMENT_URL }}
|
|
COMMENT_IS_EDIT: ${{ inputs.COMMENT_IS_EDIT }}
|
|
run: |
|
|
# 1) Find the thread by PR number. A rate-limited or unauthorized
|
|
# response carries no thread list at all, which under `bash -e` aborts
|
|
# the step (jq cannot iterate null, nor parse an HTML error page)
|
|
# rather than reaching the skip below. It is also not the same thing as
|
|
# this PR having no thread, so it is reported rather than swallowed.
|
|
threads=$(curl -s -H "Authorization: Bot $BOT_TOKEN" \
|
|
"https://discord.com/api/v10/guilds/${GUILD_ID}/threads/active")
|
|
if ! echo "$threads" | jq -e 'has("threads")' >/dev/null 2>&1; then
|
|
echo "::warning::Discord returned no thread list; the comment on PR #${PR_NUMBER} was not relayed: ${threads:0:200}"
|
|
exit 0
|
|
fi
|
|
thread_id=$(echo "$threads" | jq -r \
|
|
--arg cid "$CHANNEL_ID" \
|
|
--arg pref "#${PR_NUMBER}:" \
|
|
'(.threads // [])[] | select(.parent_id == $cid and (.name | startswith($pref))) | .id')
|
|
|
|
if [ -z "$thread_id" ]; then
|
|
echo "Thread not found for PR #${PR_NUMBER}, skipping"
|
|
exit 0
|
|
fi
|
|
|
|
# 2) Truncate comment body to fit Discord's 2000 char limit
|
|
# Reserve space for the author line + link (~100 chars)
|
|
max_body=1800
|
|
if [ ${#COMMENT_BODY} -gt $max_body ]; then
|
|
# For bot comments, show the tail (conclusions/code tend to be at the end)
|
|
if [[ "$COMMENT_AUTHOR" == *"[bot]"* ]] || [[ "$COMMENT_AUTHOR" == *"-bot"* ]]; then
|
|
truncated_body="...${COMMENT_BODY: -$max_body}"
|
|
else
|
|
truncated_body="${COMMENT_BODY:0:$max_body}..."
|
|
fi
|
|
else
|
|
truncated_body="$COMMENT_BODY"
|
|
fi
|
|
|
|
# 3) Build the message content
|
|
if [ "$COMMENT_IS_EDIT" = "true" ]; then
|
|
message=$(printf '**%s** [edited comment](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body")
|
|
else
|
|
message=$(printf '**%s** [commented](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body")
|
|
fi
|
|
payload=$(jq -n --arg content "$message" '{content: $content, flags: 4, allowed_mentions: {parse: []}}')
|
|
|
|
# 4) If this is an edit, try to find and update the existing Discord message
|
|
if [ "$COMMENT_IS_EDIT" = "true" ]; then
|
|
# Search recent messages in the thread for one containing the comment URL
|
|
messages=$(curl -s -H "Authorization: Bot $BOT_TOKEN" \
|
|
"https://discord.com/api/v10/channels/${thread_id}/messages?limit=100")
|
|
existing_msg_id=$(echo "$messages" | jq -r \
|
|
--arg url "$COMMENT_URL" \
|
|
'[.[] | select(.content | contains($url))] | first | .id // empty')
|
|
|
|
if [ -n "$existing_msg_id" ]; then
|
|
echo "Updating existing Discord message $existing_msg_id"
|
|
curl -s -X PATCH \
|
|
-H "Authorization: Bot $BOT_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload" \
|
|
"https://discord.com/api/v10/channels/${thread_id}/messages/${existing_msg_id}"
|
|
exit 0
|
|
fi
|
|
echo "Original Discord message not found, posting as new message"
|
|
fi
|
|
|
|
# 5) Post a new message to the thread
|
|
curl -s -X POST \
|
|
-H "Authorization: Bot $BOT_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload" \
|
|
"https://discord.com/api/v10/channels/${thread_id}/messages"
|