(options: {
number: number;
labels: readonly string[];
proof: Pick<RealBehaviorProof, "evidenceKind">;
dryRun: boolean;
})
| 11315 | } |
| 11316 | |
| 11317 | function syncRealBehaviorProofMediaLabels(options: { |
| 11318 | number: number; |
| 11319 | labels: readonly string[]; |
| 11320 | proof: Pick<RealBehaviorProof, "evidenceKind">; |
| 11321 | dryRun: boolean; |
| 11322 | }): { labels: string[]; changed: boolean } { |
| 11323 | const nextLabels = nextRealBehaviorProofMediaLabels(options.labels, options.proof); |
| 11324 | const currentLabelKeys = new Set(options.labels.map((label) => label.toLowerCase())); |
| 11325 | const nextLabelKeys = new Set(nextLabels.map((label) => label.toLowerCase())); |
| 11326 | const labelsToAdd = nextLabels.filter( |
| 11327 | (label) => PROOF_MEDIA_LABEL_NAMES.has(label) && !currentLabelKeys.has(label.toLowerCase()), |
| 11328 | ); |
| 11329 | const labelsToRemove = options.labels.filter( |
| 11330 | (label) => PROOF_MEDIA_LABEL_NAMES.has(label) && !nextLabelKeys.has(label.toLowerCase()), |
| 11331 | ); |
| 11332 | const changed = labelsToAdd.length > 0 || labelsToRemove.length > 0; |
| 11333 | if (!changed) return { labels: nextLabels, changed }; |
| 11334 | if (options.dryRun) return { labels: nextLabels, changed }; |
| 11335 | const syncedLabels = [...options.labels]; |
| 11336 | for (const label of labelsToRemove) { |
| 11337 | try { |
| 11338 | ghWithRetry(["issue", "edit", String(options.number), "--remove-label", label]); |
| 11339 | const index = syncedLabels.indexOf(label); |
| 11340 | if (index >= 0) syncedLabels.splice(index, 1); |
| 11341 | } catch (error) { |
| 11342 | if (!missingLabelError(error, label)) throw error; |
| 11343 | console.warn( |
| 11344 | `Skipping optional label sync for ${label}: ${ |
| 11345 | error instanceof Error ? error.message : String(error) |
| 11346 | }`, |
| 11347 | ); |
| 11348 | } |
| 11349 | } |
| 11350 | for (const label of labelsToAdd) { |
| 11351 | if (!ensureRealBehaviorProofMediaLabel(label)) continue; |
| 11352 | if (tryAddOptionalLabel({ number: options.number, label, currentLabels: syncedLabels })) { |
| 11353 | syncedLabels.push(label); |
| 11354 | } |
| 11355 | } |
| 11356 | return { |
| 11357 | labels: syncedLabels, |
| 11358 | changed: |
| 11359 | syncedLabels.length !== options.labels.length || |
| 11360 | syncedLabels.some((label, index) => label !== options.labels[index]), |
| 11361 | }; |
| 11362 | } |
| 11363 | |
| 11364 | function isAutomationReportAuthor(author: string | undefined): boolean { |
| 11365 | return Boolean(author && (/\[bot\]$/i.test(author) || author.startsWith("app/"))); |
no test coverage detected