{ "kind": "open-flow-workflow", "version": 1, "name": "Create a Sentry Release and List Releases", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_b334aa1990deda0a": { "name": "Create a release", "kind": "start", "inputDefs": [] }, "n_309ffd6191674d7e": { "inputs": { "organization": { "kind": "value", "value": "YOUR_ORGANIZATION" }, "project": { "kind": "value", "value": "YOUR_PROJECT" }, "version": { "kind": "value", "value": "0.0.1" }, "releaseUrl": { "kind": "value", "value": "https://example.com/release" } }, "name": "Configure the release", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "organization" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "project" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "version" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "releaseUrl" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "organization" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "version" } ], "name": "Configure the release", "moduleId": "n_b5aed3834a5a359d", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_a28de49548e20853": { "inputs": { "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_309ffd6191674d7e", "output": "endpoint" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_309ffd6191674d7e", "output": "body" } ] } }, "name": "Create the Sentry release", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": {}, "nullable": true, "handle": "body" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "Create the Sentry release", "executor": { "providerId": "sentry", "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "kind": "connector-proxy" } } }, "n_6d0b238e156ef592": { "inputs": { "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a28de49548e20853", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a28de49548e20853", "output": "body" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a28de49548e20853", "output": "bodyEncoding" } ] }, "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_309ffd6191674d7e", "output": "endpoint" } ] }, "version": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_309ffd6191674d7e", "output": "version" } ] } }, "name": "Confirm the release", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "version" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "release" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "endpoint" } ], "name": "Confirm the release", "moduleId": "n_c952b2521ffd2a9a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_ff6864ea380754c1": { "inputs": { "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6d0b238e156ef592", "output": "endpoint" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ff6864ea380754c1", "output": "state" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ef4f9a6aade04536", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ef4f9a6aade04536", "output": "body" } ] }, "headers": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ef4f9a6aade04536", "output": "headers" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ef4f9a6aade04536", "output": "bodyEncoding" } ] } }, "name": "Track release pages", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "integer" }, "nullable": true, "handle": "statusCode" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": true, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": false, "handle": "query" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "releases" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "name": "Track release pages", "moduleId": "n_d7394a55aa1d1f52", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_51bcb1041a8ffca6": { "inputs": {}, "name": "More release pages", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_ff6864ea380754c1", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_ef4f9a6aade04536": { "inputs": { "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6d0b238e156ef592", "output": "endpoint" } ] }, "query": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ff6864ea380754c1", "output": "query" } ] } }, "name": "Read one release page", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": true, "handle": "query" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "Read one release page", "executor": { "providerId": "sentry", "method": "GET", "query": { "mode": "object", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "kind": "connector-proxy" } } }, "n_7a7698a811546cde": { "name": "Created release and all releases", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "release" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "releases" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "outputMappings": { "release": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6d0b238e156ef592", "output": "release" } ] }, "releases": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ff6864ea380754c1", "output": "releases" } ] }, "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ff6864ea380754c1", "output": "count" } ] } } } }, "edges": [ { "source": "n_b334aa1990deda0a", "target": "n_309ffd6191674d7e" }, { "source": "n_309ffd6191674d7e", "target": "n_a28de49548e20853" }, { "source": "n_a28de49548e20853", "target": "n_6d0b238e156ef592" }, { "source": "n_6d0b238e156ef592", "target": "n_ff6864ea380754c1" }, { "source": "n_ff6864ea380754c1", "target": "n_51bcb1041a8ffca6" }, { "source": "n_51bcb1041a8ffca6", "target": "n_ef4f9a6aade04536", "sourceHandle": "more" }, { "source": "n_ef4f9a6aade04536", "target": "n_ff6864ea380754c1" }, { "source": "n_51bcb1041a8ffca6", "target": "n_7a7698a811546cde", "sourceHandle": "otherwise" } ] } }, "modules": { "n_b5aed3834a5a359d": { "name": "Configure the release", "imports": [], "source": "export default function sentryReleasePlan({ organization, project, version, releaseUrl }) {\n for (const [name,value] of Object.entries({ organization,project,version,releaseUrl })) if (typeof value !== 'string' || !value.trim() || value.startsWith('YOUR_')) throw new Error('Set '+name);\n if (!/^https:\\/\\//i.test(releaseUrl)) throw new Error('Set a public HTTPS release URL');\n return { endpoint: '/organizations/' + encodeURIComponent(organization) + '/releases/', body: { version, projects: [project], url: releaseUrl }, organization, version };\n}\n" }, "n_c952b2521ffd2a9a": { "name": "Confirm the release", "imports": [], "source": "export default function sentryReleaseCreated({ statusCode, body, bodyEncoding, endpoint, version }) {\n if (!Number.isInteger(statusCode) || statusCode < 200 || statusCode >= 300 || bodyEncoding != null || !body || typeof body !== 'object' || Array.isArray(body) || body.version !== version) throw new Error('Sentry did not confirm the release; inspect it before retrying');\n return { release: body, endpoint };\n}\n" }, "n_d7394a55aa1d1f52": { "name": "Track release pages", "imports": [], "source": "export default function sentryReleasePages({ endpoint, previousState, statusCode, body, headers, bodyEncoding }) {\n let state;\n if (previousState == null) state = { started: false, done: false, cursor: '', seen: [], releases: [] };\n else {\n if (!previousState.started || previousState.done || !Array.isArray(previousState.seen) || !Array.isArray(previousState.releases)) throw new Error('Invalid Sentry pagination state');\n if (!Number.isInteger(statusCode) || statusCode < 200 || statusCode >= 300 || bodyEncoding != null || !Array.isArray(body) || !headers || typeof headers.link !== 'string') throw new Error('Sentry page or pagination Link is invalid');\n const links = [...headers.link.matchAll(/<([^>]+)>\\s*;\\s*([^,]*)/g)];\n const next = links.filter(m => /(?:^|;)\\s*rel=\"?next\"?(?:\\s*;|\\s*$)/i.test(m[2]));\n if (next.length !== 1) throw new Error('Sentry pagination next link is missing or ambiguous');\n const params = next[0][2];\n const results = params.match(/(?:^|;)\\s*results=\"?(true|false)\"?(?:\\s*;|\\s*$)/i);\n if (!results) throw new Error('Sentry pagination results flag is missing');\n let cursor = '', done = results[1].toLowerCase() === 'false';\n if (!done) {\n const prefix = 'https://sentry.io/api/0' + endpoint + '?';\n const href = next[0][1];\n if (!href.startsWith(prefix)) throw new Error('Sentry next link does not match the requested endpoint');\n const pairs = href.slice(prefix.length).split('&').filter(Boolean), cursors = pairs.filter(p => p.startsWith('cursor='));\n if (cursors.length !== 1) throw new Error('Sentry cursor is missing or ambiguous');\n try { cursor = decodeURIComponent(cursors[0].slice(7)); } catch { throw new Error('Invalid Sentry cursor encoding'); }\n if (!cursor || previousState.seen.includes(cursor)) throw new Error('Sentry cursor repeated or empty');\n }\n state = { started: true, done, cursor, seen: done ? previousState.seen : previousState.seen.concat([cursor]), releases: previousState.releases.concat(body) };\n }\n return { state: { ...state, started: true }, more: !state.done, query: state.cursor ? { cursor: state.cursor } : {}, releases: state.releases, count: state.releases.length };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }