* Main handler factory for create_issue * Returns a message handler function that processes individual create_issue messages * @type {HandlerFactoryFunction}
(config = {})
| 517 | * @type {HandlerFactoryFunction} |
| 518 | */ |
| 519 | async function main(config = {}) { |
| 520 | // Extract configuration |
| 521 | const envLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(Boolean) : []; |
| 522 | const allowedIssueFields = parseAllowedIssueFields(config.allowed_fields); |
| 523 | const envAssignees = config.assignees ? (Array.isArray(config.assignees) ? config.assignees : config.assignees.split(",")).map(assignee => String(assignee).trim()).filter(Boolean) : []; |
| 524 | const titlePrefix = config.title_prefix ?? ""; |
| 525 | const expiresHours = config.expires ? parseInt(String(config.expires), 10) : 0; |
| 526 | const maxCount = config.max ?? 10; |
| 527 | const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); |
| 528 | const groupEnabled = parseBoolTemplatable(config.group, false); |
| 529 | const closeOlderIssuesEnabled = parseBoolTemplatable(config.close_older_issues, false); |
| 530 | const groupByDayEnabled = parseBoolTemplatable(config.group_by_day, false); |
| 531 | let deduplicateByTitle; |
| 532 | try { |
| 533 | deduplicateByTitle = parseDeduplicateByTitle(config.deduplicate_by_title); |
| 534 | } catch (error) { |
| 535 | throw new Error(`${ERR_VALIDATION}: ${getErrorMessage(error)}`); |
| 536 | } |
| 537 | const rawCloseOlderKey = config.close_older_key ? String(config.close_older_key) : ""; |
| 538 | const closeOlderKey = rawCloseOlderKey ? normalizeCloseOlderKey(rawCloseOlderKey) : ""; |
| 539 | if (rawCloseOlderKey && !closeOlderKey) { |
| 540 | throw new Error(`${ERR_VALIDATION}: close-older-key "${rawCloseOlderKey}" is invalid: it must contain at least one alphanumeric character after normalization`); |
| 541 | } |
| 542 | const includeFooter = parseBoolTemplatable(config.footer, true); |
| 543 | |
| 544 | // Create an authenticated GitHub client. Uses config["github-token"] when set |
| 545 | // (for cross-repository operations), otherwise falls back to the step-level github. |
| 546 | const githubClient = await createAuthenticatedGitHubClient(config); |
| 547 | let allowedMentionAliases = []; |
| 548 | if (Array.isArray(config.allowedMentionAliases)) { |
| 549 | allowedMentionAliases = config.allowedMentionAliases; |
| 550 | } else if (config.mentions != null) { |
| 551 | allowedMentionAliases = await resolveAllowedMentionsFromPayload(context, githubClient, core, config.mentions); |
| 552 | } |
| 553 | |
| 554 | // Check if copilot assignment is enabled |
| 555 | const assignCopilot = process.env.GH_AW_ASSIGN_COPILOT === "true"; |
| 556 | |
| 557 | // Lazily-initialised client for copilot assignment (only allocated when needed). |
| 558 | // Uses GH_AW_ASSIGN_TO_AGENT_TOKEN (agent token preference chain) when available, |
| 559 | // otherwise falls back to the step-level github object. |
| 560 | /** @type {Object|null} */ |
| 561 | let copilotClient = null; |
| 562 | |
| 563 | // Check if we're in staged mode |
| 564 | const isStaged = isStagedMode(config); |
| 565 | |
| 566 | core.info(`Default target repo: ${defaultTargetRepo}`); |
| 567 | if (allowedRepos.size > 0) { |
| 568 | core.info(`Allowed repos: ${Array.from(allowedRepos).join(", ")}`); |
| 569 | } |
| 570 | if (envLabels.length > 0) { |
| 571 | core.info(`Default labels: ${envLabels.join(", ")}`); |
| 572 | } |
| 573 | if (envAssignees.length > 0) { |
| 574 | core.info(`Default assignees: ${envAssignees.join(", ")}`); |
| 575 | } |
| 576 | if (allowedIssueFields.length > 0 && !allowedIssueFields.includes("*")) { |
nothing calls this directly
no test coverage detected