Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/scripts/intl-pipeline/lib/workflows/pr-creation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,21 @@ function generateInitialPRBody(): string {
].join("\n")
}

export interface RunFailure {
locale: string
file: string
message: string
}

/**
* Generate a run summary to append to the PR body
*/
export function generateRunSummary(
langCodes: string[],
committedFiles: CommittedFile[],
mode: string,
workflowRunUrl?: string
workflowRunUrl?: string,
failures: RunFailure[] = []
): string {
const now = new Date().toISOString().replace("T", " ").slice(0, 19) + " UTC"

Expand All @@ -71,6 +78,20 @@ export function generateRunSummary(
parts.push(`- [View workflow run](${workflowRunUrl})`)
}

if (failures.length > 0) {
parts.push("", `**${failures.length} task(s) failed:**`, "")
for (const f of failures) {
parts.push(`- \`${f.file}\` (${f.locale}): ${f.message}`)
}
parts.push("", "Rerun the failed combinations:", "", "```")
for (const f of failures) {
parts.push(
`gh workflow run "Intl Pipeline" -f target_path="${f.file}" -f target_languages="${f.locale}"`
)
}
parts.push("```")
}

parts.push("")
return parts.join("\n")
}
Expand Down Expand Up @@ -99,7 +120,8 @@ export async function createOrUpdateTranslationPR(
branch: string,
committedFiles: CommittedFile[],
languagePairs: LanguagePair[],
mode: string
mode: string,
failures: RunFailure[] = []
): Promise<{ number: number; html_url: string }> {
logSection("Pull Request")

Expand All @@ -109,7 +131,8 @@ export async function createOrUpdateTranslationPR(
langCodes,
committedFiles,
mode,
workflowRunUrl
workflowRunUrl,
failures
)

// Check for existing open PR
Expand Down
55 changes: 44 additions & 11 deletions src/scripts/intl-pipeline/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,11 @@ async function main() {

const committedFiles: Array<{ path: string; content: string }> = []
let hasCommits = false
// Per-task failures captured with file context. Populated by submitWithContext
// wrapper so we can report rich failure info in the PR body and surface
// copy-pasteable rerun commands. Pool's own error tracking still runs in
// parallel for the orchestration-level "did anything fail" check.
const failures: Array<{ locale: string; file: string; message: string }> = []

// Resolve target paths in five passes:
// 1. Normalize (log-level: auto-prefix and strip accidental locale paths)
Expand Down Expand Up @@ -783,6 +788,24 @@ async function main() {
},
})

// Wraps pool.submit to attach file context to any thrown error -- the pool
// only knows the locale; we want (locale, file, reason) for PR reporting.
const submitWithContext = (
locale: string,
filePath: string,
fn: () => Promise<TaskResult | void>
) => {
pool.submit(locale, async () => {
try {
return await fn()
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
failures.push({ locale, file: filePath, message })
throw err
}
})
}

// Submit all file x language tasks to the pool
for (const file of englishFiles) {
for (const locale of targetLanguages) {
Expand Down Expand Up @@ -815,7 +838,7 @@ async function main() {
continue
}

pool.submit(locale, () =>
submitWithContext(locale, file.path, () =>
runFullTranslation(
file,
locale,
Expand All @@ -839,7 +862,7 @@ async function main() {

if (config.stampOnly) {
log(`[${locale}] ${file.path}: stamp only`)
pool.submit(locale, async () => {
submitWithContext(locale, file.path, async () => {
const sourceManifest =
file.type === "markdown"
? buildMarkdownManifest(file.content, file.path, baseBranchSha)
Expand All @@ -854,7 +877,7 @@ async function main() {
continue
}

pool.submit(locale, () =>
submitWithContext(locale, file.path, () =>
runIncremental(
file,
locale,
Expand All @@ -872,15 +895,24 @@ async function main() {
// Wait for all tasks to complete
await pool.drain()

// Check for task failures
if (pool.hasErrors()) {
const errors = pool.getErrors()
console.error(`[pipeline] ${errors.length} task(s) failed:`)
for (const { language, error } of errors) {
console.error(` [${language}] ${error.message}`)
// Log task failures but don't abort -- partial successes ship. Failures are
// recorded with file context (via submitWithContext) and surfaced in the PR
// body with rerun commands. Per-file manifests are only stamped on success,
// so a rerun of just the failed combinations naturally retries them without
// touching the work that landed this run.
if (failures.length > 0) {
log(`${failures.length} task(s) failed (continuing with successes):`, "warn")
for (const f of failures) {
log(` [${f.locale}] ${f.file}: ${f.message}`, "warn")
}
}

// Hard abort only if literally nothing succeeded -- a fully-failed run
// shouldn't produce an empty PR. (committedFiles excludes manifest-only stamp
// commits, so we also check hasCommits for the stamp-only path.)
if (failures.length > 0 && committedFiles.length === 0 && !hasCommits) {
throw new Error(
`Pipeline aborted: ${errors.length} translation task(s) failed. Temp branch ${tempBranch} preserved with partial progress.`
`Pipeline aborted: all ${failures.length} translation task(s) failed. Temp branch ${tempBranch} preserved.`
)
}

Expand Down Expand Up @@ -942,7 +974,8 @@ async function main() {
targetBranch,
committedFiles,
languagePairs,
config.mode
config.mode,
failures
)
} catch (error) {
console.warn(
Expand Down
Loading