ReviewOS

stacks/bun-plugin-auto-imports

generateRuntimeIndex emits a duplicate export name, and the whole barrel then fails to load

#1185
Closed glennmichael123 opened this 9 days ago ·
9 days ago

What happens

When two files in a scanned directory export the same name, generateRuntimeIndex writes both into the generated barrel:

export { reviewFormError, difficultyLabel, ... } from '../../../resources/functions/review-form'
export { difficultyIsEstimated, difficultyLabel, difficultyTitle } from '../../../resources/functions/trail-difficulty'

That module cannot be parsed — SyntaxError: Cannot export a duplicate name 'difficultyLabel' — so nothing it exports is available, not just the colliding name.

Why

generateRuntimeIndex groups exports by file and emits one export { … } from per file, with no check for a name another file already contributed:

const exportsByFile = new Map
for (const exp of allExports) { /* grouped by exp.file */ }
for (const [file, exports] of exportsByFile) {
  // emits `export { ...valueExports } from cleanPath`
}

Worth noting that @stacksjs/server's own combinedBarrel already solves this one level up: it finds names owned by more than one barrel, picks a winner, and emits an explicit re-export under a comment about TS2308. The per-file runtime index has no equivalent.

What it cost downstream

In an app with 408 auto-imported names, adding a second difficultyLabel took out every one of them. The consumer sees one warning line and then failures that look unrelated to the change:

  • GET /api/trails/:id answered 500, because the action referenced an auto-imported function that was now undefined
  • 18 browser tests failed across avatars, feed, metadata, planning and recording — none of them near the two files involved

The collision also stayed invisible in the committed barrel, since that file predated both source files. It appeared only once the generator ran, i.e. on the first run after a checkout.

Reproducing

  1. In a scanned directory, create two files that each export the same name:
    // a.ts
    export function label(value: unknown): string { return String(value) }
    // b.ts
    export function label(value: unknown, extra: unknown): string { return `${value}${extra}` }
  2. Run the generator.
  3. The emitted barrel contains label twice and throws on import.

Suggested direction

Rather than emitting a module that cannot parse, do what combinedBarrel does: track names already emitted, and for a collision either skip the later one with a comment naming both files, or alias it. Either leaves the other 407 names working and makes the clash legible at the point it is introduced.

A clear error at generation time would also be a large improvement on the current failure mode, which surfaces as unrelated 500s at runtime.

Versions

  • bun-plugin-auto-imports 0.4.3
  • @stacksjs/server 0.75.63 (calls generateRuntimeIndex)

Sign in to comment on this issue.