Skip to content

Detect conflicting output paths across build steps - #321

Draft
bcomnes wants to merge 1 commit into
masterfrom
issue-288-output-conflicts
Draft

Detect conflicting output paths across build steps#321
bcomnes wants to merge 1 commit into
masterfrom
issue-288-output-conflicts

Conversation

@bcomnes

@bcomnes bcomnes commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #288.

Enforce one producer per destination-relative output path across pages, generated pages, templates, static assets, copied directories, esbuild bundles, service workers, and generated metadata. Conflicts fail with DOM_STACK_ERROR_OUTPUT_CONFLICT and identify the output path and both producers instead of silently choosing whichever write finishes last.

Implementation

  • Add a shared output registry with separator normalization, destination-filesystem case handling, file-versus-directory checks, and duplicate-record handling distinct from duplicate writes.
  • Claim esbuild outputs before buffered emission and isolate copy sources before publication; attribute identifiable native esbuild entry collisions to both sources.
  • Reserve page outputs before concurrent template rendering, including dynamic arrays and async iterators.
  • Keep ownership for untouched watch producers, compute replacement ownership once in the page worker, and revalidate against current ownership before serialized promotion.
  • Consolidate copy-watch startup into one inventory, buffer startup changes, and preserve independent mappings from overlapping copy roots.
  • Release obsolete ownership after successful replacements and removals, and replace the complete ownership map on full watch builds.
  • Preserve manifest-hook read-after-write behavior and public copy-report destination mappings.

Scope and tradeoffs

Ordinary one-shot builds do not stage or roll back the whole site: earlier successful writes may remain after a later failure, but a conflicting second producer cannot overwrite the first. Incremental page phases, full watch builds, and copy isolation retain staging where needed for checked publication. Stages use the destination filesystem.

This is a cross-cutting build/watch change rather than a small validation check. It adds ownership bookkeeping, buffered output, temporary disk usage, and a separate promotion lock to coordinate esbuild with page workers. Performance has not been benchmarked.

Publication is not atomic against filesystem I/O failures. Arbitrary filesystem writes by user code or plugins bypass the registry, separate instances are not coordinated when publishing to the same destination, and unattributable esbuild plugin/shared-chunk collisions retain native diagnostics. Initial copy/esbuild failures abort watch startup and require restarting; conflicts in a successfully started session can recover after correcting the source.

Validation

  • Full Node suite: 237 passed, 0 failed, 2 skipped. Skips require a source filesystem capable of representing case aliases.
  • Repository-wide ESLint: passed.
  • TypeScript: passed.
  • Installed dependency check: passed during the simplification validation pass.
  • git diff --check: passed.
  • Playwright: 11 passed, 2 failed at the known All recipes and All migrations documentation-navigation timeouts. Both failures were previously reproduced on clean HEAD; unrelated browser behavior was left unchanged.

Regression coverage includes cross-producer collisions, dynamic template outputs, filtered watch conflicts, esbuild/page promotion races, startup copy changes, rename/removal recovery, overlapping copy roots, hook read-back, copy-report mappers, and recovery after promotion I/O failure.

Review focus

  • Output ownership replacement and cleanup across filtered and full watch builds.
  • Copy inventory/startup event draining and overlapping source roots.
  • The retained staging boundaries and explicit non-atomic publication guarantee.
  • Compatibility of controlled esbuild emission with custom settings and plugins.

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34791088100

Coverage increased (+0.5%) to 94.769%

Details

  • Coverage increased (+0.5%) from the base build.
  • Patch coverage: 14 uncovered changes across 5 files (991 of 1005 lines covered, 98.61%).
  • 1 coverage regression across 1 file.

Uncovered Changes

File Changed Covered %
index.js 268 260 97.01%
lib/build-pages/index.js 150 147 98.0%
lib/build-esbuild/index.js 113 112 99.12%
lib/build-esbuild/output-conflicts.js 70 69 98.57%
lib/output-registry.js 174 173 99.43%
Total (13 files) 1005 991 98.61%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
index.js 1 95.51%

Coverage Stats

Coverage Status
Relevant Lines: 9797
Covered Lines: 9438
Line Coverage: 96.34%
Relevant Branches: 2667
Covered Branches: 2374
Branch Coverage: 89.01%
Branches in Coverage %: Yes
Coverage Strength: 406.73 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect conflicting output paths across build steps

2 participants