* Build a shared handler factory for missing issue handlers. * Encapsulates the common search-or-create issue pipeline, differing only in * template paths, item field names, and item renderers. * * @param {Object} options * @param {string} options.handlerType - Handler type identifier used in l
(options)
| 29 | * @returns {HandlerFactoryFunction} |
| 30 | */ |
| 31 | function buildMissingIssueHandler(options) { |
| 32 | const { handlerType, defaultTitlePrefix, itemsField, templatePath, templateListKey, buildCommentHeader, renderCommentItem, renderIssueItem, defaultLabels = [] } = options; |
| 33 | |
| 34 | return async function main(config = {}) { |
| 35 | // Extract configuration |
| 36 | // create_issue: templatable boolean — default true. |
| 37 | // Accepts: literal boolean (true/false), string 'true'/'false', or a GitHub Actions |
| 38 | // expression (e.g. '${{ inputs.create-incomplete-issue }}'). Expressions are evaluated |
| 39 | // by GitHub Actions before this handler runs, so config.create_issue holds the |
| 40 | // resolved boolean or string value when the handler executes. |
| 41 | const createIssue = parseBoolTemplatable(config.create_issue, true); |
| 42 | const titlePrefix = config.title_prefix || defaultTitlePrefix; |
| 43 | const userLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(label => label) : []; |
| 44 | const envLabels = [...new Set([...defaultLabels, ...userLabels])]; |
| 45 | const maxCount = config.max || 1; // Default to 1 to create only one issue per workflow run |
| 46 | |
| 47 | core.info(`Title prefix: ${titlePrefix}`); |
| 48 | if (envLabels.length > 0) { |
| 49 | core.info(`Default labels: ${envLabels.join(", ")}`); |
| 50 | } |
| 51 | core.info(`Max count: ${maxCount}`); |
| 52 | |
| 53 | // Track how many items we've processed for max limit |
| 54 | let processedCount = 0; |
| 55 | |
| 56 | // Track created/updated issues |
| 57 | const processedIssues = []; |
| 58 | |
| 59 | /** |
| 60 | * Create or update an issue for the missing items |
| 61 | * @param {string} workflowName - Name of the workflow |
| 62 | * @param {string} workflowSource - Source path of the workflow |
| 63 | * @param {string} workflowSourceURL - URL to the workflow source |
| 64 | * @param {string} runUrl - URL to the workflow run |
| 65 | * @param {Array<Object>} items - Array of missing item objects |
| 66 | * @returns {Promise<Object>} Result with success/error status |
| 67 | */ |
| 68 | async function createOrUpdateIssue(workflowName, workflowSource, workflowSourceURL, runUrl, items) { |
| 69 | const { owner, repo } = context.repo; |
| 70 | |
| 71 | // Create issue title |
| 72 | const issueTitle = `${titlePrefix} ${workflowName}`; |
| 73 | |
| 74 | core.info(`Checking for existing issue with title: "${issueTitle}"`); |
| 75 | |
| 76 | // Search for existing open issue with this title |
| 77 | const searchQuery = `repo:${owner}/${repo} is:issue is:open in:title "${issueTitle}"`; |
| 78 | |
| 79 | try { |
| 80 | const searchResult = await github.rest.search.issuesAndPullRequests({ |
| 81 | q: searchQuery, |
| 82 | per_page: 1, |
| 83 | }); |
| 84 | |
| 85 | if (searchResult.data.total_count > 0) { |
| 86 | // Issue exists, add a comment |
| 87 | const existingIssue = searchResult.data.items[0]; |
| 88 | core.info(`Found existing issue #${existingIssue.number}: ${existingIssue.html_url}`); |
no test coverage detected