diff --git a/actions/setup/js/collect_ndjson_output.cjs b/actions/setup/js/collect_ndjson_output.cjs index 16ab413fcdd..e04f92dfb36 100644 --- a/actions/setup/js/collect_ndjson_output.cjs +++ b/actions/setup/js/collect_ndjson_output.cjs @@ -326,6 +326,16 @@ async function main() { const typeConfig = expectedOutputTypes[itemType]; const normalizeIssueClosingKeywords = typeConfig !== null && typeof typeConfig === "object" && typeConfig.normalize_closing_keywords === true; + if (itemType === "dispatch_workflow") { + const hasWorkflowName = typeof item.workflow_name === "string" && item.workflow_name.trim().length > 0; + if (!hasWorkflowName && typeConfig !== null && typeof typeConfig === "object" && Array.isArray(typeConfig.workflows)) { + const { workflows: configuredWorkflows } = typeConfig; + if (configuredWorkflows.length === 1 && typeof configuredWorkflows[0] === "string" && configuredWorkflows[0].trim().length > 0) { + item.workflow_name = configuredWorkflows[0].trim(); + core.info(`[INGESTION] Line ${i + 1}: Inferred dispatch_workflow workflow_name='${item.workflow_name}' from safe-outputs config`); + } + } + } // Use the validation engine to validate the item if (hasValidationConfig(itemType)) { diff --git a/actions/setup/js/collect_ndjson_output.test.cjs b/actions/setup/js/collect_ndjson_output.test.cjs index 044e7fb53d7..641a3681107 100644 --- a/actions/setup/js/collect_ndjson_output.test.cjs +++ b/actions/setup/js/collect_ndjson_output.test.cjs @@ -119,6 +119,13 @@ describe("collect_ndjson_output.cjs", () => { defaultMax: 1, fields: { tag: { type: "string", sanitize: !0, maxLength: 256 }, operation: { required: !0, type: "string", enum: ["replace", "append", "prepend"] }, body: { required: !0, type: "string", sanitize: !0, maxLength: 65e3 } }, }, + dispatch_workflow: { + defaultMax: 1, + fields: { + workflow_name: { required: !0, type: "string", sanitize: !0, minLength: 1, maxLength: 256, pattern: ".*\\S.*", patternError: "must not be empty" }, + inputs: { type: "object" }, + }, + }, }) )); }), @@ -195,6 +202,32 @@ describe("collect_ndjson_output.cjs", () => { const parsedOutput = JSON.parse(outputCall[1]); (expect(parsedOutput.items).toHaveLength(2), expect(parsedOutput.items[0].type).toBe("create_issue"), expect(parsedOutput.items[1].type).toBe("add_comment"), expect(parsedOutput.errors).toHaveLength(0)); }), + it("should infer dispatch_workflow workflow_name when one workflow is configured", async () => { + const testFile = "/tmp/gh-aw/test-ndjson-output.txt", + ndjsonContent = '{"type": "dispatch_workflow", "inputs": {"message": "hello"}}'; + (fs.writeFileSync(testFile, ndjsonContent), (process.env.GH_AW_SAFE_OUTPUTS = testFile)); + const __config = '{"dispatch_workflow":{"workflows":["workflow-handler"]}}', + configPath = "/tmp/gh-aw/safeoutputs/config.json"; + (fs.mkdirSync("/tmp/gh-aw/safeoutputs", { recursive: !0 }), fs.writeFileSync(configPath, __config), await eval(`(async () => { ${collectScript}; await main(); })()`)); + const setOutputCalls = mockCore.setOutput.mock.calls, + outputCall = setOutputCalls.find(call => "output" === call[0]); + expect(outputCall).toBeDefined(); + const parsedOutput = JSON.parse(outputCall[1]); + (expect(parsedOutput.items).toHaveLength(1), expect(parsedOutput.items[0].type).toBe("dispatch_workflow"), expect(parsedOutput.items[0].workflow_name).toBe("workflow-handler"), expect(parsedOutput.errors).toHaveLength(0)); + }), + it("should not infer dispatch_workflow workflow_name when multiple workflows are configured", async () => { + const testFile = "/tmp/gh-aw/test-ndjson-output.txt", + ndjsonContent = '{"type": "dispatch_workflow", "inputs": {"message": "hello"}}'; + (fs.writeFileSync(testFile, ndjsonContent), (process.env.GH_AW_SAFE_OUTPUTS = testFile)); + const __config = '{"dispatch_workflow":{"workflows":["worker", " "]}}', + configPath = "/tmp/gh-aw/safeoutputs/config.json"; + (fs.mkdirSync("/tmp/gh-aw/safeoutputs", { recursive: !0 }), fs.writeFileSync(configPath, __config), await eval(`(async () => { ${collectScript}; await main(); })()`)); + const setOutputCalls = mockCore.setOutput.mock.calls, + outputCall = setOutputCalls.find(call => "output" === call[0]); + expect(outputCall).toBeDefined(); + const parsedOutput = JSON.parse(outputCall[1]); + expect(parsedOutput.errors).toHaveLength(1); + }), it("should preserve Slack mrkdwn links in custom safe-job string inputs", async () => { const testFile = "/tmp/gh-aw/test-ndjson-output.txt"; const slackText = "Tracking issue: ";