{ "kind": "open-flow-workflow", "version": 1, "name": "GitHub Release Slack Alerts", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_2bd6eaf5307e0b39": { "name": "Daily release check", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 0 * * *", "timezone": "UTC" } ] }, "n_fcd687fa1f4a9bca": { "inputs": { "repositories": { "kind": "value", "value": [ { "owner": "n8n-io", "repo": "n8n" }, { "owner": "home-assistant", "repo": "core" } ] } }, "name": "Read latest releases", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "owner": { "type": "string", "minLength": 1 }, "repo": { "type": "string", "minLength": 1 } }, "required": [ "owner", "repo" ], "additionalProperties": false } }, "nullable": false, "handle": "repositories" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "owner": { "type": "string" }, "repo": { "type": "string" }, "release": { "type": "object", "properties": { "id": { "type": "integer", "description": "The release ID." }, "tag_name": { "type": "string", "description": "The release tag name." }, "name": { "type": [ "string", "null" ], "description": "The release name." }, "body": { "type": [ "string", "null" ], "description": "The release body." }, "draft": { "type": "boolean", "description": "Whether the release is a draft." }, "prerelease": { "type": "boolean", "description": "Whether the release is a prerelease." }, "html_url": { "type": "string", "description": "The release URL." }, "assets_url": { "type": "string", "description": "The assets API URL." }, "tarball_url": { "type": [ "string", "null" ], "description": "The tarball download URL." }, "zipball_url": { "type": [ "string", "null" ], "description": "The zipball download URL." }, "target_commitish": { "type": "string", "description": "The target branch or commit SHA." }, "author": { "type": "object", "properties": { "id": { "type": "integer", "description": "The user ID." }, "login": { "type": "string", "description": "The username." }, "avatar_url": { "type": "string", "description": "The avatar URL." }, "html_url": { "type": "string", "description": "The profile URL." }, "type": { "type": "string", "description": "The account type." } }, "required": [ "id", "login" ], "additionalProperties": true, "description": "The release author." }, "created_at": { "type": "string", "description": "The creation timestamp." }, "published_at": { "type": [ "string", "null" ], "description": "The publication timestamp." } }, "required": [ "id", "tag_name" ], "additionalProperties": true, "description": "A GitHub release record." } }, "required": [ "owner", "repo", "release" ], "additionalProperties": false } }, "nullable": false, "handle": "releases" } ], "name": "Read latest releases", "moduleId": "n_487862944f677cd9", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "github.get_latest_release" } ] } ] } }, "n_517a9d67d290ab59": { "inputs": { "releases": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_fcd687fa1f4a9bca", "output": "releases" } ] } }, "name": "Select releases from last day", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array" }, "nullable": false, "handle": "releases" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "text": { "type": "string", "description": "Plain text message content. When blocks are provided, Slack uses this as notification and accessibility fallback text." }, "sourceIndex": { "type": "integer" } }, "required": [ "text", "sourceIndex" ], "additionalProperties": false } }, "nullable": false, "handle": "notifications" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "name": "Select releases from last day", "moduleId": "n_d4725e82691f40fa", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_6cac7f1e3f068f92": { "inputs": {}, "name": "Has recent releases", "kind": "condition", "matchMode": "first", "cases": [ { "output": "recent", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_517a9d67d290ab59", "output": "count" } }, "operator": ">", "right": { "kind": "value", "value": 0 } } ] } ] } ] }, "n_6fbab58454404c15": { "inputs": { "notifications": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_517a9d67d290ab59", "output": "notifications" } ] }, "channelId": { "kind": "value", "value": "YOUR_SLACK_CHANNEL_ID" } }, "name": "Send release alerts", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array" }, "nullable": false, "handle": "notifications" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "ts": { "type": "string", "description": "The timestamp identifier of the posted message." }, "channelId": { "type": "string", "description": "The channel ID where the message was posted." } }, "required": [ "ts", "channelId" ], "additionalProperties": false, "description": "The output payload for a posted Slack message." } }, "nullable": false, "handle": "sent" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "name": "Send release alerts", "moduleId": "n_01c9650c72dba164", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "slack.post_message" } ] } ] } } }, "edges": [ { "source": "n_2bd6eaf5307e0b39", "target": "n_fcd687fa1f4a9bca" }, { "source": "n_fcd687fa1f4a9bca", "target": "n_517a9d67d290ab59" }, { "source": "n_517a9d67d290ab59", "target": "n_6cac7f1e3f068f92" }, { "source": "n_6cac7f1e3f068f92", "target": "n_6fbab58454404c15", "sourceHandle": "recent" } ] } }, "modules": { "n_487862944f677cd9": { "name": "Read latest releases", "imports": [], "source": "export default async function readReleases({ repositories }, context) {\n const releases = [];\n for (const { owner, repo } of repositories) {\n const release = await context.actions.github.get_latest_release({ owner, repo });\n releases.push({ owner, repo, release });\n }\n return { releases };\n}\n" }, "n_d4725e82691f40fa": { "name": "Select releases from last day", "imports": [], "source": "export default function recentReleases({ releases }) {\n const cutoff = Date.now() - 24 * 60 * 60 * 1000;\n const escape = (value) => value.replace(/&/g, '&').replace(//g, '>');\n const notifications = [];\n for (const [index, { owner, repo, release }] of releases.entries()) {\n const published = typeof release.published_at === 'string' ? Date.parse(release.published_at) : NaN;\n if (!Number.isFinite(published)) throw new Error('Release is missing a valid publication timestamp');\n if (!(published > cutoff)) continue;\n const name = release.name || release.tag_name;\n if (typeof name !== 'string' || !name || typeof release.html_url !== 'string' || !release.html_url) throw new Error('Release is missing its name or URL');\n const body = release.body ?? '';\n if (typeof body !== 'string') throw new Error('Release body must be text or null');\n notifications.push({\n text: `:tada: New release for *${escape(owner + '/' + repo)}* - ${escape(name)}\\n\\n${escape(body.slice(0, 500))}\\n\\n${release.html_url}`,\n sourceIndex: index,\n });\n }\n return { notifications, count: notifications.length };\n}\n" }, "n_01c9650c72dba164": { "name": "Send release alerts", "imports": [], "source": "export default async function sendReleaseAlerts({ notifications, channelId }, context) {\n const sent = [];\n for (const { text } of notifications) {\n sent.push(await context.actions.slack.post_message({ channelId, text }));\n }\n return { sent, count: sent.length };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }