Skip to content

Commit 3ec45dc

Browse files
gnodetclaude
andcommitted
Add resilient ENOENT handling for parallel build races
When mvnd runs tests in parallel with the docs build, ephemeral directories like .camel-jbang/work can be created and deleted during glob scanning, causing ENOENT errors that abort the entire stream and result in missing symlinks (e.g. 47 of 71 expected files). Add a resilientSrc() wrapper around gulp.src() that: - Catches ENOENT errors and continues processing remaining files - Uses a 'close' event handler as fallback to end the passthrough stream if the source stops without emitting 'end' - Adds .camel-jbang to the ignore patterns alongside target - Adds diagnostic logging showing symlink counts per destination Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c9ac60d commit 3ec45dc

1 file changed

Lines changed: 38 additions & 2 deletions

File tree

docs/gulpfile.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,37 @@ const tasks = Array.from(sourcesMap).flatMap(([type, definition]) => {
315315
})
316316
}
317317

318+
// Wraps gulp.src to gracefully handle ENOENT from ephemeral directories
319+
// (e.g. .camel-jbang/work created/deleted by tests running in parallel).
320+
// We pipe through a passthrough transform so that on ENOENT we can
321+
// cleanly end() the passthrough, which properly signals downstream
322+
// pipes to finish. Direct error handling on gulp.src() doesn't work
323+
// because Node.js .pipe() does not propagate errors or end signals.
324+
const ignorePatterns = ['**/target/**', '**/.camel-jbang/**']
325+
const resilientSrc = (source, options) => {
326+
const src = gulp.src(source, options)
327+
const passthrough = through2.obj(function (file, enc, done) {
328+
done(null, file)
329+
})
330+
src.on('error', function (err) {
331+
if (err.code === 'ENOENT') {
332+
// Don't end passthrough — the source may continue with remaining files.
333+
// If it stops, the 'close' handler below will clean up.
334+
console.error(`⚠️ ENOENT (skipped): ${err.path || err.message}`)
335+
} else {
336+
passthrough.destroy(err)
337+
}
338+
})
339+
// If the source closes without emitting 'end' (e.g. after an error),
340+
// ensure the passthrough ends so downstream tasks don't hang.
341+
src.on('close', function () {
342+
if (!passthrough.writableEnded) {
343+
passthrough.end()
344+
}
345+
})
346+
return src.pipe(passthrough)
347+
}
348+
318349
// creates symlinks from source to destination that satisfy the
319350
// given filter removing the basedir from a path, i.e. symlinking
320351
// from a flat hiearchy
@@ -327,10 +358,12 @@ const tasks = Array.from(sourcesMap).flatMap(([type, definition]) => {
327358
}
328359
})
329360

330-
return gulp.src(source, { ignore: ['**/target/**'] })
361+
let fileCount = 0
362+
return resilientSrc(source, { ignore: ignorePatterns, allowEmpty: true })
331363
.pipe(filterFn)
332364
.pipe(
333365
map((file, done) => {
366+
fileCount++
334367
// this flattens the output to just .../pages/.../file.ext
335368
// instead of .../pages/camel-.../src/main/docs/.../file.ext
336369
file.base = path.dirname(file.path)
@@ -340,6 +373,9 @@ const tasks = Array.from(sourcesMap).flatMap(([type, definition]) => {
340373
.pipe(gulp.symlink(destination, {
341374
relativeSymlinks: true,
342375
}))
376+
.on('end', () => {
377+
console.log(` → symlinked ${fileCount} files to ${destination}`)
378+
})
343379
}
344380

345381
// generates sorted & grouped nav.adoc file from a set of .adoc
@@ -410,7 +446,7 @@ const tasks = Array.from(sourcesMap).flatMap(([type, definition]) => {
410446
return done()
411447
}
412448

413-
return gulp.src(source, { ignore: ['**/target/**'] }) // asciidoc files
449+
return resilientSrc(source, { ignore: ignorePatterns, allowEmpty: true }) // asciidoc files
414450
.pipe(through2.obj(extractExamples)) // extracted example files
415451
// symlink links from a fixed directory, i.e. we could link to
416452
// the example files from `destination`, that would not work for

0 commit comments

Comments
 (0)