{ "kind": "open-flow-workflow", "version": 1, "name": "GitHub Release Slack Notifications", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_40b22e8e6c1cf672": { "name": "Receive a GitHub release event", "kind": "integration", "config": { "events": { "kind": "value", "value": [ "release" ] }, "insecureSsl": { "kind": "value", "value": false }, "owner": { "kind": "value", "value": "example-owner" }, "repo": { "kind": "value", "value": "example-repository" } }, "definition": { "configInputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string", "enum": [ "*", "branch_protection_configuration", "branch_protection_rule", "check_run", "check_suite", "code_scanning_alert", "commit_comment", "create", "custom_property_values", "delete", "dependabot_alert", "deploy_key", "deployment", "deployment_status", "discussion", "discussion_comment", "fork", "gollum", "issue_comment", "issue_dependencies", "issues", "label", "member", "meta", "milestone", "package", "page_build", "project", "project_card", "project_column", "public", "pull_request", "pull_request_review", "pull_request_review_comment", "pull_request_review_thread", "push", "registry_package", "release", "repository", "repository_advisory", "repository_import", "repository_ruleset", "repository_vulnerability_alert", "secret_scanning_alert", "secret_scanning_alert_location", "secret_scanning_scan", "security_and_analysis", "star", "status", "sub_issues", "team_add", "watch", "workflow_job", "workflow_run" ] }, "minItems": 1, "uniqueItems": true }, "nullable": false, "handle": "events" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "insecureSsl", "value": false }, { "jsonSchema": { "type": "string", "pattern": "^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$" }, "nullable": false, "handle": "owner" }, { "jsonSchema": { "type": "string", "pattern": "^(?!\\.{1,2}$)[a-zA-Z0-9._-]{1,100}$" }, "nullable": false, "handle": "repo" } ], "definitionVersion": 2, "description": "Triggers when selected GitHub webhook events occur in a repository.", "displayName": "Repository Event", "key": "github.on_repo_event", "name": "on_repo_event", "outputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "deliveryId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "event" } ], "provider": "github", "type": "integration", "endpoint": { "body": { "allowArray": false, "allowEmpty": false, "formats": [ "json" ] }, "methods": [ "POST" ], "successStatus": 202 } } }, "n_736bdbbe92520c29": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_40b22e8e6c1cf672", "output": "body" } ] } }, "name": "Format the complete release notice", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string" } }, "nullable": false, "handle": "parts" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "action" } ], "name": "Format the complete release notice", "moduleId": "n_2cf5e2a1b50c2d07", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_d648a156f4472aeb": { "inputs": { "parts": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_736bdbbe92520c29", "output": "parts" } ] }, "channelId": { "kind": "value", "value": "YOUR_SLACK_CHANNEL_ID" }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d648a156f4472aeb", "output": "state" } ] }, "messageTs": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d5b6b6a22f57495a", "output": "ts" } ] }, "messageChannelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d5b6b6a22f57495a", "output": "channelId" } ] } }, "name": "Prepare the next Slack message", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string" } }, "nullable": false, "handle": "parts" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "messageTs" }, { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "messageChannelId" } ], "outputs": [ { "jsonSchema": { "type": "object", "properties": { "index": { "type": "integer", "minimum": 0 }, "receipts": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "required": [ "index", "receipts" ], "additionalProperties": false }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "text" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "name": "Prepare the next Slack message", "moduleId": "n_60dbb7c5c6cfef02", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_22d87f45f45abed5": { "inputs": {}, "name": "More release text to send", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_d648a156f4472aeb", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_d5b6b6a22f57495a": { "inputs": { "channelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d648a156f4472aeb", "output": "channelId" } ] }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d648a156f4472aeb", "output": "text" } ] } }, "name": "Post one release message to Slack", "kind": "task", "task": { "inputs": [ { "description": "The Slack conversation or channel ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" }, { "description": "Plain text message content. When blocks are provided, Slack uses this as notification and accessibility fallback text.", "jsonSchema": { "type": "string", "description": "Plain text message content. When blocks are provided, Slack uses this as notification and accessibility fallback text." }, "nullable": true, "handle": "text", "value": null }, { "description": "Slack Block Kit blocks to render in the message.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Slack Block Kit block object. Pass the block exactly as Slack documents it." }, "minItems": 1, "description": "Slack Block Kit blocks to render in the message." }, "nullable": true, "handle": "blocks", "value": null }, { "description": "Slack legacy attachments to include in the message.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Slack legacy attachment object. Prefer blocks for new messages when possible." }, "minItems": 1, "description": "Slack legacy attachments to include in the message." }, "nullable": true, "handle": "attachments", "value": null }, { "description": "Whether Slack should unfurl links in the message.", "jsonSchema": { "type": "boolean", "description": "Whether Slack should unfurl links in the message." }, "nullable": true, "handle": "unfurlLinks", "value": null }, { "description": "Whether Slack should unfurl media in the message.", "jsonSchema": { "type": "boolean", "description": "Whether Slack should unfurl media in the message." }, "nullable": true, "handle": "unfurlMedia", "value": null }, { "description": "Slack message metadata to attach to the message.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Slack message metadata to attach to the message." }, "nullable": true, "handle": "metadata", "value": null } ], "outputs": [ { "description": "The timestamp identifier of the posted message.", "jsonSchema": { "type": "string", "description": "The timestamp identifier of the posted message." }, "nullable": false, "handle": "ts" }, { "description": "The channel ID where the message was posted.", "jsonSchema": { "type": "string", "description": "The channel ID where the message was posted." }, "nullable": false, "handle": "channelId" } ], "name": "Post one release message to Slack", "executor": { "kind": "connector", "action": "slack.post_message" } } }, "n_7e04879f0b2e52b1": { "name": "Release notification receipts", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "receipts" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "action" } ], "outputMappings": { "receipts": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d648a156f4472aeb", "output": "state", "field": "receipts" } ] }, "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d648a156f4472aeb", "output": "count" } ] }, "action": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_736bdbbe92520c29", "output": "action" } ] } } } }, "edges": [ { "source": "n_40b22e8e6c1cf672", "target": "n_736bdbbe92520c29" }, { "source": "n_736bdbbe92520c29", "target": "n_d648a156f4472aeb" }, { "source": "n_d648a156f4472aeb", "target": "n_22d87f45f45abed5" }, { "source": "n_22d87f45f45abed5", "target": "n_d5b6b6a22f57495a", "sourceHandle": "more" }, { "source": "n_d5b6b6a22f57495a", "target": "n_d648a156f4472aeb" }, { "source": "n_22d87f45f45abed5", "target": "n_7e04879f0b2e52b1", "sourceHandle": "otherwise" } ] } }, "modules": { "n_2cf5e2a1b50c2d07": { "name": "Format the complete release notice", "imports": [], "source": "export default function githubReleaseParts({ body }) {\n if (!body || typeof body !== 'object' || Array.isArray(body) || typeof body.action !== 'string' || !body.action || !body.repository || !body.release) throw new Error('Expected a GitHub release event');\n const repository = body.repository.full_name, tag = body.release.tag_name, notes = body.release.body, link = body.release.html_url;\n if (typeof repository !== 'string' || !repository.trim() || typeof tag !== 'string' || !tag.trim() || (notes != null && typeof notes !== 'string') || typeof link !== 'string') throw new Error('The release is missing its repository, tag, notes or URL');\n if (!/^https:\\/\\/[^\\s/?#@<>\"'\\\\]+(?:[/?#][^\\s<>\"'\\\\]*)?$/.test(link)) throw new Error('Expected the release’s real HTTPS URL without credentials');\n const text = 'New release is available in ' + repository + ' !\\n' + tag + ' Details:\\n' + (notes ?? '') + '\\n\\nLink: ' + link;\n const chunks = [];\n let chunk = '';\n for (const character of text) {\n if (chunk.length + character.length > 3900) { chunks.push(chunk); chunk = ''; }\n chunk += character;\n }\n if (chunk) chunks.push(chunk);\n const parts = chunks.map((content, index) => chunks.length > 1 ? 'Part ' + (index + 1) + '/' + chunks.length + '\\n' + content : content);\n if (parts.some(part => part.length > 4000)) throw new Error('The release part exceeds the message budget');\n return { parts, action: body.action };\n}\n" }, "n_60dbb7c5c6cfef02": { "name": "Prepare the next Slack message", "imports": [], "source": "export default function githubReleaseProgress({ parts, previousState, messageTs, messageChannelId, channelId }) {\n if (!Array.isArray(parts) || !parts.length || typeof channelId !== 'string' || !channelId.trim()) throw new Error('Expected the release parts and destination channel ID');\n let state;\n if (previousState == null) state = { index: 0, receipts: [] };\n else {\n if (!Number.isInteger(previousState.index) || previousState.index < 1 || previousState.index > parts.length || !Array.isArray(previousState.receipts) || previousState.receipts.length !== previousState.index - 1) throw new Error('Invalid release notification progress');\n if (typeof messageTs !== 'string' || !/^\\d+\\.\\d+$/.test(messageTs) || messageChannelId !== channelId) throw new Error('Slack did not confirm the release notification in the requested channel; posting may already have occurred');\n state = { index: previousState.index, receipts: [...previousState.receipts, { ts: messageTs, channelId: messageChannelId }] };\n }\n const text = parts[state.index];\n if (text == null) return { state, more: false, text: '', channelId, count: state.receipts.length };\n if (typeof text !== 'string' || !text || text.length > 4000) throw new Error('Invalid release message part');\n return { state: { index: state.index + 1, receipts: state.receipts }, more: true, text, channelId, count: state.receipts.length };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }