* Create a message-level close-entity handler function. * * Centralises the common close-flow pipeline: * 1. Max-count gating * 2. Comment body resolution (item.body → config.comment fallback) * 3. Content sanitization * 4. Target repository / entity number resolution (via callbacks.re
(config, entityConfig, callbacks, githubClient)
| 263 | * @returns {import('./types/handler-factory').MessageHandlerFunction} Message handler function |
| 264 | */ |
| 265 | function createCloseEntityHandler(config, entityConfig, callbacks, githubClient) { |
| 266 | const requiredLabels = config.required_labels || []; |
| 267 | const requiredTitlePrefix = config.required_title_prefix || ""; |
| 268 | const maxCount = config.max || 10; |
| 269 | const comment = config.comment || ""; |
| 270 | const isStaged = isStagedMode(config); |
| 271 | const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config); |
| 272 | const allowBody = config.allow_body !== false; // default true; false only when explicitly set to false |
| 273 | |
| 274 | let processedCount = 0; |
| 275 | |
| 276 | return async function handleCloseEntity(message, resolvedTemporaryIds) { |
| 277 | // 1. Max-count gating |
| 278 | if (processedCount >= maxCount) { |
| 279 | core.warning(`Skipping ${entityConfig.itemType}: max count of ${maxCount} reached`); |
| 280 | return { success: false, error: `Max count of ${maxCount} reached` }; |
| 281 | } |
| 282 | processedCount++; |
| 283 | |
| 284 | const item = message; |
| 285 | |
| 286 | // Log message structure for debugging (avoid logging body content) |
| 287 | const logFields = { has_body: !!item.body, body_length: item.body ? item.body.length : 0 }; |
| 288 | if (item[entityConfig.numberField] !== undefined) { |
| 289 | logFields[entityConfig.numberField] = item[entityConfig.numberField]; |
| 290 | } |
| 291 | if (item.repo !== undefined) { |
| 292 | logFields.has_repo = true; |
| 293 | } |
| 294 | core.info(`Processing ${entityConfig.itemType} message: ${JSON.stringify(logFields)}`); |
| 295 | |
| 296 | // 2. Comment body resolution |
| 297 | /** @type {string|undefined} */ |
| 298 | let commentToPost; |
| 299 | /** @type {string} */ |
| 300 | let commentSource = "unknown"; |
| 301 | |
| 302 | if (!allowBody) { |
| 303 | // allow-body: false — drop any body the agent provided and skip the comment |
| 304 | if (typeof item.body === "string" && item.body.trim() !== "") { |
| 305 | core.warning(`${entityConfig.itemType}: allow-body is false — dropping non-empty body (length=${item.body.length}) and closing without a comment`); |
| 306 | } else { |
| 307 | core.info(`${entityConfig.itemType}: allow-body is false — closing without a comment`); |
| 308 | } |
| 309 | commentToPost = undefined; |
| 310 | } else if (typeof item.body === "string" && item.body.trim() !== "") { |
| 311 | commentToPost = item.body; |
| 312 | commentSource = "item.body"; |
| 313 | } else if (typeof comment === "string" && comment.trim() !== "") { |
| 314 | commentToPost = comment; |
| 315 | commentSource = "config.comment"; |
| 316 | } else { |
| 317 | core.warning("No comment body provided in message and no default comment configured"); |
| 318 | return { success: false, error: "No comment body provided" }; |
| 319 | } |
| 320 | |
| 321 | if (commentToPost !== undefined) { |
| 322 | core.info(`Comment body determined: length=${commentToPost.length}, source=${commentSource}`); |
no test coverage detected