8
n8n 中文网amn8n.com

高级 n8n 工作流与 GitHub 同步

高级

这是一个DevOps, Multimodal AI领域的自动化工作流,包含 38 个节点。主要使用 If, N8n, Set, Code, Merge 等节点。 使用 GitHub 的智能变更检测自动化工作流备份

前置要求
  • GitHub Personal Access Token
  • Telegram Bot Token
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "dmvTNU9rfNdgTeSp",
  "meta": {
    "instanceId": "e7cc7f71b8002726158f14502c7243f892bdf0befb8af4790197437e4666e71e"
  },
  "name": "高级 n8n 工作流与 GitHub 同步",
  "tags": [],
  "nodes": [
    {
      "id": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
      "name": "遍历项目",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -368,
        912
      ],
      "parameters": {
        "options": {}
      },
      "executeOnce": false,
      "typeVersion": 3
    },
    {
      "id": "34869a4d-b687-44f7-81fd-5f71e8679a0e",
      "name": "更新文件内容并提交",
      "type": "n8n-nodes-base.github",
      "position": [
        1584,
        1872
      ],
      "webhookId": "f2d754dd-b68d-41e8-a662-7e91c1c3aa95",
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.owner }}"
        },
        "filePath": "={{ $json.context.newFile.path }}",
        "resource": "file",
        "operation": "edit",
        "repository": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.name }}"
        },
        "fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
        "commitMessage": "=update: {{ $json.context.newFile.name }}"
      },
      "credentials": {
        "githubApi": {
          "id": "7sCS6E9S2UWO6PFt",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "bbf77bc7-8c35-4177-be4f-44c91682fd1a",
      "name": "计划触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2912,
        864
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "34d67ff9-e25a-42a9-b751-dbde37ed575b",
      "name": "获取所有工作流",
      "type": "n8n-nodes-base.n8n",
      "position": [
        -1328,
        848
      ],
      "parameters": {
        "filters": {},
        "requestOptions": {}
      },
      "credentials": {
        "n8nApi": {
          "id": "M9BEPZyx4jMbY5tY",
          "name": "n8n account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "98fc6907-d188-4f9a-b68e-44592bfac95b",
      "name": "编码 N8N 工作流",
      "type": "n8n-nodes-base.code",
      "position": [
        -1104,
        848
      ],
      "parameters": {
        "jsCode": "// Encode workflow data to base64 to prevent data pollution\nconst items = $input.all();\n\nfor (const item of items) {\n  const originalWorkflow = item.json;\n\n  item.json = {\n    id: originalWorkflow.id,\n    name: originalWorkflow.name,\n    n8nWorkflowData: Buffer.from(JSON.stringify(originalWorkflow)).toString('base64')\n  };\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "3576b4b3-6692-4d08-9453-697d44ac0f56",
      "name": "决策变更",
      "type": "n8n-nodes-base.code",
      "position": [
        -112,
        928
      ],
      "parameters": {
        "jsCode": "// Helper function to ensure stable JSON serialization for reliable comparison.\nfunction sortKeysDeep(obj) {\n  if (obj === null || typeof obj !== 'object') return obj;\n  if (Array.isArray(obj)) return obj.map(sortKeysDeep);\n  const out = {};\n  Object.keys(obj).sort().forEach(k => { out[k] = sortKeysDeep(obj[k]); });\n  return out;\n}\n\nconst items = $input.all();\nconst WORKFLOWS_DIR = $node[\"Configuration\"].json.repo.path;\n\nfor (const item of items) {\n  const src = item.json || {};\n  const flags = {\n    fileExists: false,\n    nameChanged: false,\n    shouldCommit: false\n  };\n  // Initialize the context container\n  item.json.context = {\n    oldFile: { path: '', name: '' },\n    newFile: { path: '', name: '' },\n    operation: ''\n  };\n  const context = item.json.context;\n\n  // 1. Determine if the file exists on GitHub.\n  const hasGithub = typeof src.githubWorkflowData === 'string' && src.githubWorkflowData.length > 0;\n  flags.fileExists = hasGithub;\n\n  // 2. Extract the current workflow name from the N8N data.\n  const currentName = src.name || '';\n  context.newFile.name = currentName;\n\n  // 3. Detect renames and set file paths.\n  if (typeof src.filePath === 'string' && src.filePath.length > 0) {\n    const parts = src.filePath.split('/');\n    const filename = parts.pop() || '';\n    const githubName = filename.endsWith('.json') ? filename.slice(0, -5) : filename;\n    \n    flags.nameChanged = githubName !== currentName;\n    context.oldFile.path = src.filePath;\n    context.oldFile.name = githubName;\n    \n    const dirPath = parts.join('/');\n    context.newFile.path = `${dirPath}/${currentName}.json`;\n\n  } else {\n    flags.nameChanged = false;\n    context.newFile.path = `${WORKFLOWS_DIR}/${currentName}.json`.replace(/\\/+/g, '/');\n  }\n\n  // 4. Perform a stable comparison to see if a commit is needed.\n  try {\n    if (flags.fileExists) {\n      const n8nJsonStr = Buffer.from(src.n8nWorkflowData, 'base64').toString('utf8');\n      const githubJsonStr = Buffer.from(src.githubWorkflowData, 'base64').toString('utf8');\n      const n8nObj = JSON.parse(n8nJsonStr);\n      const githubObj = JSON.parse(githubJsonStr);\n      const stableN8nStr = JSON.stringify(sortKeysDeep(n8nObj));\n      const stableGithubStr = JSON.stringify(sortKeysDeep(githubObj));\n      flags.shouldCommit = stableN8nStr !== stableGithubStr;\n    } else {\n      flags.shouldCommit = true; // New file, always commit.\n    }\n  } catch (e) {\n    flags.shouldCommit = true;   // If parsing or comparison fails, better to commit.\n  }\n\n  // 5. Determine the final operation type.\n  if (flags.nameChanged) {\n    context.operation = 'rename';\n  } else if (!flags.fileExists) {\n    context.operation = 'create';\n  } else if (flags.shouldCommit) {\n    context.operation = 'update';\n  } else {\n    context.operation = 'skip';\n  }\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "1cc5a35b-8e52-46dd-ac69-03e320f66fc8",
      "name": "删除旧文件",
      "type": "n8n-nodes-base.github",
      "position": [
        1744,
        1408
      ],
      "webhookId": "1bd59af3-c8ee-4664-9cfd-df3ab4b6793d",
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.owner }}"
        },
        "filePath": "={{ $json.context.oldFile.path }}",
        "resource": "file",
        "operation": "delete",
        "repository": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.name }}"
        },
        "commitMessage": "=rename: {{ $json.context.oldFile.name }} -> {{ $json.context.newFile.name }} (step 1/2: remove old)"
      },
      "credentials": {
        "githubApi": {
          "id": "7sCS6E9S2UWO6PFt",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c6464638-d7d4-4e36-b359-f70412db2bbb",
      "name": "创建新文件(重命名)",
      "type": "n8n-nodes-base.github",
      "position": [
        2144,
        1408
      ],
      "webhookId": "1b7a1463-d11a-4c0c-a596-a4b09e003d5a",
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.owner }}"
        },
        "filePath": "={{ $json.context.newFile.path }}",
        "resource": "file",
        "repository": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.name }}"
        },
        "fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
        "commitMessage": "=rename: {{ $json.context.oldFile.name }} -> {{ $json.context.newFile.name }} (step 2/2: create new)"
      },
      "credentials": {
        "githubApi": {
          "id": "7sCS6E9S2UWO6PFt",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b470dd47-762a-4c8a-90e0-e4f535e9b41e",
      "name": "创建后合并(重命名)",
      "type": "n8n-nodes-base.merge",
      "notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
      "position": [
        2384,
        1392
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "preferInput1"
            }
          }
        },
        "joinMode": "enrichInput1",
        "fieldsToMatchString": "id"
      },
      "typeVersion": 3.2
    },
    {
      "id": "be267762-77a4-4af5-ba26-7f3945d44b32",
      "name": "更新后合并",
      "type": "n8n-nodes-base.merge",
      "notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
      "position": [
        1872,
        1856
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "preferInput1"
            }
          }
        },
        "joinMode": "enrichInput1",
        "fieldsToMatchString": "id"
      },
      "typeVersion": 3.2
    },
    {
      "id": "db1f95ae-42e5-4588-bb7d-5348011d9afe",
      "name": "列出文件",
      "type": "n8n-nodes-base.github",
      "notes": "An edge case handling. Do not stop the whole workflow if there's no such folder.",
      "onError": "continueErrorOutput",
      "position": [
        -1552,
        1040
      ],
      "webhookId": "2e1f9567-52d4-4047-980c-6b4a57d4bd40",
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $(\"Configuration\").item.json.repo.owner }}"
        },
        "filePath": "={{ $(\"Configuration\").item.json.repo.path }}",
        "resource": "file",
        "operation": "list",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $(\"Configuration\").item.json.repo.name }}"
        }
      },
      "credentials": {
        "githubApi": {
          "id": "7sCS6E9S2UWO6PFt",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "8c6f381b-caed-4267-abb4-6672c93f45e9",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        -880,
        928
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "preferInput1"
            }
          }
        },
        "joinMode": "enrichInput1",
        "fieldsToMatchString": "id"
      },
      "typeVersion": 3.2
    },
    {
      "id": "8d8f0963-0b37-47d9-b263-ffc7da06d118",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        1168
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 448,
        "content": "## 重命名文件(两步法)"
      },
      "typeVersion": 1
    },
    {
      "id": "371212bd-c19c-4908-b3cb-fe75034a95fb",
      "name": "便签 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1616,
        752
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 448,
        "content": "## 收集数据"
      },
      "typeVersion": 1
    },
    {
      "id": "fb300a39-10b0-48cf-a793-a96e1ee3014f",
      "name": "便签 2",
      "type": "n8n-nodes-base.stickyNote",
      "disabled": true,
      "position": [
        -2672,
        592
      ],
      "parameters": {
        "color": 4,
        "width": 326,
        "height": 448,
        "content": "## 设置参数"
      },
      "typeVersion": 1
    },
    {
      "id": "80dabce7-e36e-4ea7-bd7d-67fb3b3096f8",
      "name": "便签 3",
      "type": "n8n-nodes-base.stickyNote",
      "disabled": true,
      "position": [
        -3024,
        592
      ],
      "parameters": {
        "color": 4,
        "width": 326,
        "height": 448,
        "content": "## 调整计划"
      },
      "typeVersion": 1
    },
    {
      "id": "802494dc-8c76-4a34-97d3-c414cb04a179",
      "name": "便签 4",
      "type": "n8n-nodes-base.stickyNote",
      "disabled": true,
      "position": [
        176,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 1174,
        "height": 272,
        "content": "## 执行报告"
      },
      "typeVersion": 1
    },
    {
      "id": "d0af7c02-a586-4993-8c4b-8fb18c3053fe",
      "name": "构建摘要数组",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        112
      ],
      "parameters": {
        "jsCode": "// Aggregate arrays and flags for summary (no rendering)\n\nfunction normalizeName(item) {\n  return String(\n    item.json?.name || 'unknown'\n  );\n}\n\nconst items = $input.all();\n\nconst buckets = { create: [], update: [], rename: [], skip: [] };\n\nfor (const it of items) {\n  const name = normalizeName(it);\n  const op = it.json?.context.operation;\n  const oldName = it.json?.context.oldFile.name || name;\n  const newName = it.json?.context.newFile.name || name;\n\n  if (op === 'rename') buckets.rename.push(`${oldName} -> ${newName}`);\n  else if (op === 'create') buckets.create.push(name);\n  else if (op === 'update') buckets.update.push(name);\n  else buckets.skip.push(name);\n}\n\nconst isAnythingChanged = buckets.create.length > 0 || buckets.update.length > 0 || buckets.rename.length > 0;\n\nreturn [{ json: {\n  isAnythingChanged,\n  created: buckets.create,\n  updated: buckets.update,\n  renamed: buckets.rename,\n  skipped: buckets.skip\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "0c401771-b522-4091-a14f-aba4a06d6158",
      "name": "渲染摘要",
      "type": "n8n-nodes-base.code",
      "notes": "## Connecting to a messenger\n\nUse {{$json.message}} as message text. 'isAnythingChanged' controls whether to send.",
      "position": [
        928,
        112
      ],
      "parameters": {
        "jsCode": "// Helper function to escape text for Telegram's MarkdownV2 parser\nconst escapeMarkdownV2 = (str) => {\n  // For the full list of characters, see https://core.telegram.org/bots/api#markdownv2-style\n  return String(str).replace(/([_\\[\\]()~`>#+=|{}.!*-])/g, '\\\\$1');\n};\n\nconst all = $input.all();\nconst data = (all[0] && all[0].json) || {};\n\nconst config = $('Configuration').first().json;\nconst repoOwner = config.repo.owner;\nconst repoName = config.repo.name;\nconst repoPath = config.repo.path;\n\n// Construct repository URL using /blob/-/ for a branch-agnostic link\nconst repoUrl = `https://github.com/${repoOwner}/${repoName}/blob/-/${repoPath}`;\n\n// The link's *text* must be escaped, but the URL must not be.\nconst repoLinkText = escapeMarkdownV2(`${repoOwner}/${repoName}/${repoPath}`);\nconst repoLink = `[${repoLinkText}](${repoUrl})`;\n\nconst getList = key => (Array.isArray(data[key]) ? data[key] : []);\nconst sortAsc = (a, b) => String(a).localeCompare(String(b));\n\nconst sections = [\n  { key: 'created', title: 'Created' },\n  { key: 'updated', title: 'Updated' },\n  { key: 'renamed', title: 'Renamed' },\n  { key: 'skipped', title: 'Skipped (no changes)' },\n];\n\nconst summaryParts = [\n  `created ${getList('created').length}`,\n  `updated ${getList('updated').length}`,\n  `renamed ${getList('renamed').length}`,\n  `skipped ${getList('skipped').length}`\n];\n\n// Construct the final message with the new header format\nconst messageLines = [\n  '*Backup N8N workflows to GitHub*', // Main title\n  '', // Blank line for spacing\n  `Repo: ${repoLink}`,\n  `Totals: ${escapeMarkdownV2(summaryParts.join(', '))}`\n];\n\n// Append detailed lists as before\nfor (const { key, title } of sections) {\n  const list = [...getList(key)].sort(sortAsc);\n  if (list.length) {\n    messageLines.push('', `*${escapeMarkdownV2(title)}:*`);\n    for (const item of list) {\n      let line;\n      if (key === 'renamed') {\n        const [oldName, newName] = item.split(' -> ');\n        line = `\\`${oldName}.json\\` ${escapeMarkdownV2('->')} \\`${newName}.json\\``;\n      } else {\n        line = `\\`${item}.json\\``;\n      }\n      messageLines.push(line);\n    }\n  }\n}\n\nconst message = messageLines.join('\\n');\nreturn [{ json: { message } }];"
      },
      "notesInFlow": false,
      "typeVersion": 2
    },
    {
      "id": "fdb5e4f7-8aa1-4273-8776-ac0248b893e4",
      "name": "是否有更改?",
      "type": "n8n-nodes-base.if",
      "position": [
        704,
        112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "8148a3ce-16fe-4074-9f57-c49072be8a8f",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $node[\"Configuration\"].json.report.verbose }}",
              "rightValue": ""
            },
            {
              "id": "ee4ef204-341f-444a-a26a-299aa0cde573",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isAnythingChanged }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "13ba1e58-2bac-44dc-9142-b61efef7c4e1",
      "name": "提取工作流参数",
      "type": "n8n-nodes-base.code",
      "position": [
        -1104,
        1040
      ],
      "parameters": {
        "jsCode": "let items = $input.all();\n\nfor (let item of items) {\n    try {\n        const contentBase64 = item.json.content;\n        const path = item.json.path;\n        const sha = item.json.sha;\n\n        // Decode and parse GitHub file content to extract name\n        const content = Buffer.from(contentBase64, 'base64').toString('utf8');\n        const workflow = JSON.parse(content);\n\n        // Keep only the fields we need from GitHub side, store as base64\n        item.json = {\n            id: workflow.id,\n            name: workflow.name,\n            filePath: path,\n            githubWorkflowData: contentBase64, // Store as base64 to match N8N side\n            sha: sha\n        };\n\n    } catch (error) {\n        // Non-JSON or invalid workflow file\n        console.log(`Error parsing file ${item.json.path}: ${error.message}`);\n        item.json = {\n            id: null,\n            name: null,\n            filePath: item.json.path,\n            error: error.message\n        };\n    }\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "3464840a-599d-4432-aade-bfd5a2930461",
      "name": "获取文件",
      "type": "n8n-nodes-base.github",
      "notes": "An edge case handling. Do not stop the whole workflow if there's no such folder.",
      "onError": "continueErrorOutput",
      "position": [
        -1328,
        1040
      ],
      "webhookId": "93c8a2dd-ddad-4837-a062-25473eee1208",
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $(\"Configuration\").item.json.repo.owner }}"
        },
        "filePath": "={{ $json.path }}",
        "resource": "file",
        "operation": "get",
        "repository": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $(\"Configuration\").item.json.repo.name }}"
        },
        "asBinaryProperty": false,
        "additionalParameters": {}
      },
      "credentials": {
        "githubApi": {
          "id": "7sCS6E9S2UWO6PFt",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "d991e89f-d795-4559-9b3f-4ecce50723b5",
      "name": "是否配置了 Telegram?",
      "type": "n8n-nodes-base.if",
      "position": [
        256,
        112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "03191658-7c7b-4f85-a07b-35d9749d91f3",
              "operator": {
                "type": "number",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $(\"Configuration\").item.json.report.tg.chatID }}",
              "rightValue": 0
            },
            {
              "id": "3277a7ef-0895-4fbf-beb2-432f54cc8efc",
              "operator": {
                "type": "number",
                "operation": "notEquals"
              },
              "leftValue": "={{ $(\"Configuration\").item.json.report.tg.chatID }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0705a90f-00e1-46e6-b5ef-4a4efe5255cd",
      "name": "发送消息",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1152,
        112
      ],
      "webhookId": "ea0e343d-7ffa-4dd7-a3aa-0e7e45ad5753",
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "={{ $node[\"Configuration\"].json.report.tg.chatID }}",
        "additionalFields": {
          "parse_mode": "MarkdownV2",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "7sLcbl1lRhPnfzJI",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "fdef22d8-7960-48e2-a046-e69b3f48ce15",
      "name": "创建新文件",
      "type": "n8n-nodes-base.github",
      "position": [
        1296,
        2416
      ],
      "webhookId": "66429ae4-4b7d-4fb5-8438-26cdf6c4faa8",
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.owner }}"
        },
        "filePath": "={{ $json.context.newFile.path }}",
        "resource": "file",
        "repository": {
          "__rl": true,
          "mode": "",
          "value": "={{ $(\"Configuration\").item.json.repo.name }}"
        },
        "fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
        "commitMessage": "=create: {{ $json.context.newFile.name }}"
      },
      "credentials": {
        "githubApi": {
          "id": "7sCS6E9S2UWO6PFt",
          "name": "GitHub account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "669101cb-2d23-41c7-8e07-6e282b556cdd",
      "name": "创建后合并",
      "type": "n8n-nodes-base.merge",
      "notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
      "position": [
        1584,
        2400
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "preferInput1"
            }
          }
        },
        "joinMode": "enrichInput1",
        "fieldsToMatchString": "id"
      },
      "typeVersion": 3.2
    },
    {
      "id": "c5d3b5e4-9c2a-4cf8-8bae-fca50b9f884c",
      "name": "便签 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1328,
        1712
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 448,
        "content": "## 更新现有文件"
      },
      "typeVersion": 1
    },
    {
      "id": "d58b3267-9510-4abd-b4df-14f5cc19f984",
      "name": "便签6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        2256
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 448,
        "content": "## 创建新文件"
      },
      "typeVersion": 1
    },
    {
      "id": "2ababf2c-5315-4d6a-9f09-b2efaef4ab2c",
      "name": "无操作,不执行任何操作",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1056,
        2976
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "7e069ed2-7303-4db5-b0b3-f18edbfb88da",
      "name": "便签7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        2800
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 448,
        "content": "## 无需操作"
      },
      "typeVersion": 1
    },
    {
      "id": "0ac9743d-298a-49d2-b391-4c34b9d5122b",
      "name": "停止并报错",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        336,
        3376
      ],
      "parameters": {
        "errorMessage": "=Invalid operation: \"{{ $json.context.operation }}\". You should look at the code in the \"Decide changes\" node."
      },
      "typeVersion": 1
    },
    {
      "id": "f7a498d9-900f-41ab-9b5f-8e849b85c38c",
      "name": "便签8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        752
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 448,
        "content": "## 控制器"
      },
      "typeVersion": 1
    },
    {
      "id": "4d426ef2-c4db-4690-bd23-48c374fcf0e1",
      "name": "路由器",
      "type": "n8n-nodes-base.switch",
      "position": [
        128,
        880
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "=rename",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "5af9aafc-3ee1-4855-89b2-b0ceb83b3169",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.context.operation }}",
                    "rightValue": "rename"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "update",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "849881fc-2d0e-4154-b6c2-10ff6c2b5480",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.context.operation }}",
                    "rightValue": "update"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "create",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "85f4cce5-476e-4970-82b4-0b04cc67870f",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.context.operation }}",
                    "rightValue": "create"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "skip",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "51919025-e488-4557-9cd9-23f4be9bbf06",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.context.operation }}",
                    "rightValue": "skip"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "ignoreCase": true,
          "fallbackOutput": "extra",
          "renameFallbackOutput": "error"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "921826cc-13da-4db8-8a18-fdffacbcf5bb",
      "name": "删除后合并(重命名)",
      "type": "n8n-nodes-base.merge",
      "notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
      "position": [
        1968,
        1248
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "preferInput1"
            }
          }
        },
        "joinMode": "enrichInput1",
        "fieldsToMatchString": "id"
      },
      "typeVersion": 3.2
    },
    {
      "id": "bda0d23c-f6fe-435c-8732-c1f97c313d80",
      "name": "配置",
      "type": "n8n-nodes-base.set",
      "position": [
        -2576,
        864
      ],
      "parameters": {
        "values": {
          "number": [
            {
              "name": "report.tg.chatID",
              "value": null
            }
          ],
          "string": [
            {
              "name": "repo.owner"
            },
            {
              "name": "repo.name"
            },
            {
              "name": "repo.path",
              "value": "workflows/"
            }
          ],
          "boolean": [
            {
              "name": "report.verbose"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "a9074602-25a9-4fec-a464-caf1459dd3fb",
      "name": "便签 10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3024,
        1072
      ],
      "parameters": {
        "width": 672,
        "height": 960,
        "content": "## 高级 n8n 工作流与 GitHub 同步"
      },
      "typeVersion": 1
    },
    {
      "id": "999b6d5e-ba67-4b98-9637-1ca8c061113e",
      "name": "空配置时停止",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        -1920,
        1024
      ],
      "parameters": {
        "errorMessage": "Incomplete GitHub configuration. Please check \"Configuration\" node."
      },
      "typeVersion": 1
    },
    {
      "id": "99820b4c-3ad8-43ba-bafe-9ae5f919e39f",
      "name": "断言 GitHub 配置",
      "type": "n8n-nodes-base.if",
      "notes": "Pre-provisioning safe fuse",
      "position": [
        -2160,
        864
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "0b285299-edd5-41a0-85e8-3d94246e1cff",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.repo.owner }}",
              "rightValue": ""
            },
            {
              "id": "c9f894e0-cf42-45e1-87bd-13c2bd024b48",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.repo.name }}",
              "rightValue": ""
            },
            {
              "id": "f1591996-df67-4caf-8171-a049993268d2",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.repo.path }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "02eb2192-2fce-4283-90c0-616f2e6ced5d",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Router": {
      "main": [
        [
          {
            "node": "Delete old file",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge after delete (rename)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update file content and commit",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge after update",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create new file",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge after create",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stop and Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get files": {
      "main": [
        [
          {
            "node": "Extract workflow parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List files": {
      "main": [
        [
          {
            "node": "Get files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "Assert GitHub config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decide changes": {
      "main": [
        [
          {
            "node": "Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Render summary": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create new file": {
      "main": [
        [
          {
            "node": "Merge after create",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Delete old file": {
      "main": [
        [
          {
            "node": "Merge after delete (rename)",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Is Telegram configured?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Decide changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anything changed?": {
      "main": [
        [
          {
            "node": "Render summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all workflows": {
      "main": [
        [
          {
            "node": "Encode N8N workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge after create": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge after update": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assert GitHub config": {
      "main": [
        [
          {
            "node": "Get all workflows",
            "type": "main",
            "index": 0
          },
          {
            "node": "List files",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stop on empty config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build summary arrays": {
      "main": [
        [
          {
            "node": "Anything changed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Encode N8N workflows": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Telegram configured?": {
      "main": [
        [
          {
            "node": "Build summary arrays",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create new file (rename)": {
      "main": [
        [
          {
            "node": "Merge after create (rename)",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "No Operation, do nothing": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract workflow parameters": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge after create (rename)": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge after delete (rename)": {
      "main": [
        [
          {
            "node": "Merge after create (rename)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Create new file (rename)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update file content and commit": {
      "main": [
        [
          {
            "node": "Merge after update",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 开发运维, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量38
分类2
节点类型13
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Maksym Brashenko

Maksym Brashenko

@j2h4u

I’m a generalist engineer who thrives on messy systems, undocumented protocols, and problems no one wants to touch Over the past 6 years, I’ve built infrastructure for managing hardware, reverse-engineered flaky devices, automated workflows end to end, and connected product logic to business reality Use my link to book an initial consultation

外部链接
在 n8n.io 查看

分享此工作流