* Build a context string describing code-push failures for inclusion in failure issue/comment bodies. * Manifest file protection refusals are separated from other push failures to give them a dedicated * section with clearer remediation instructions. * @param {string} codePushFailureErrors - Newl
(codePushFailureErrors, pullRequest = null, runUrl = "")
| 772 | * @returns {string} Formatted context string, or empty string if no failures |
| 773 | */ |
| 774 | function buildCodePushFailureContext(codePushFailureErrors, pullRequest = null, runUrl = "") { |
| 775 | if (!codePushFailureErrors) { |
| 776 | return ""; |
| 777 | } |
| 778 | |
| 779 | // Split errors into protected-file protection refusals, patch size errors, patch apply failures, and other push failures |
| 780 | const manifestErrors = []; |
| 781 | const patchSizeErrors = []; |
| 782 | const patchApplyErrors = []; |
| 783 | const otherErrors = []; |
| 784 | const errorLines = codePushFailureErrors.split("\n").filter(line => line.trim()); |
| 785 | for (const errorLine of errorLines) { |
| 786 | const colonIndex = errorLine.indexOf(":"); |
| 787 | if (colonIndex !== -1) { |
| 788 | const type = errorLine.substring(0, colonIndex); |
| 789 | const error = errorLine.substring(colonIndex + 1); |
| 790 | if (error.includes("manifest files") || error.includes("protected files")) { |
| 791 | manifestErrors.push({ type, error }); |
| 792 | } else if (error.includes("Patch size") && error.includes("exceeds")) { |
| 793 | patchSizeErrors.push({ type, error }); |
| 794 | } else if (error.includes("Failed to apply patch")) { |
| 795 | patchApplyErrors.push({ type, error }); |
| 796 | } else { |
| 797 | otherErrors.push({ type, error }); |
| 798 | } |
| 799 | } |
| 800 | } |
| 801 | |
| 802 | let context = ""; |
| 803 | |
| 804 | // Protected file protection section — shown before generic failures |
| 805 | if (manifestErrors.length > 0) { |
| 806 | context += |
| 807 | "\n**🛡️ Protected Files**: The code push was refused because the patch modifies protected files (package manifests, agent instruction files, or repository security configuration). " + |
| 808 | "This protection guards against unintended supply chain changes.\n"; |
| 809 | if (pullRequest) { |
| 810 | context += `\n**Target Pull Request:** [#${pullRequest.number}](${pullRequest.html_url})\n`; |
| 811 | } |
| 812 | context += "\n**Blocked Operations:**\n"; |
| 813 | for (const { type, error } of manifestErrors) { |
| 814 | context += `- \`${type}\`: ${error}\n`; |
| 815 | } |
| 816 | // Build a dynamic YAML snippet listing only the safe output types that were actually blocked |
| 817 | const typeToYamlKey = { |
| 818 | create_pull_request: "create-pull-request", |
| 819 | push_to_pull_request_branch: "push-to-pull-request-branch", |
| 820 | }; |
| 821 | const blockedTypes = [...new Set(manifestErrors.map(e => e.type))]; |
| 822 | let yamlSnippet = "```yaml\nsafe-outputs:\n"; |
| 823 | for (const type of blockedTypes) { |
| 824 | const yamlKey = typeToYamlKey[type] || type.replace(/_/g, "-"); |
| 825 | yamlSnippet += ` ${yamlKey}:\n protected-files: fallback-to-issue\n`; |
| 826 | } |
| 827 | yamlSnippet += "```\n"; |
| 828 | context += "\n<details>\n<summary>⚙️ Configure <code>protected-files: fallback-to-issue</code></summary>\n\n"; |
| 829 | context += yamlSnippet; |
| 830 | context += "</details>\n"; |
| 831 | } |
no test coverage detected