Skip to content

fix(orval): keep generated barrels idempotent across formatter runs (#3756) - #3762

Merged
melloware merged 1 commit into
orval-labs:masterfrom
aqeelat:fix/barrel-idempotency-3756
Jul 24, 2026
Merged

fix(orval): keep generated barrels idempotent across formatter runs (#3756)#3762
melloware merged 1 commit into
orval-labs:masterfrom
aqeelat:fix/barrel-idempotency-3756

Conversation

@aqeelat

@aqeelat aqeelat commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #3756.

Problem

The shared workspace barrel (<workspace>/index.ts) and the zod verb-schema index accumulated duplicate export * lines on every regeneration when an afterAllFilesWrite formatter (e.g. prettier) ran between generations. Each run re-appended the full export set instead of being a no-op.

Root cause

Both barrels deduped re-exports by matching a single-quote formatted line (data.includes(export * from '${imp}')). orval writes single quotes; a formatter flips them to double quotes; the next run's substring check no longer matches → every export is re-appended. Unbounded growth.

Fix

Dedup on the bare module specifier (not the formatted line) via a shared quote-agnostic readReExportSpecifiers helper (packages/orval/src/utils/barrel.ts):

  • the workspace barrel and operationSchemas re-export append only specifiers not already present — append (not overwrite) is required because the barrel can share its path with the implementation target (since v8.18.0 re-exports index file if target is index.ts #3675: target === <workspace>/index.ts);
  • the zod verb-schema index rebuilds the pure barrel from the on-disk ∪ new union (a mergeBarrelSpecifiers helper backs this).

All three barrel re-export sites now share the one extractor; the operationSchemas site's duplicated inline regex is removed.

Verification

  • New regression test (generate-spec.test.ts): two projects sharing a workspace barrel, regenerate with a simulated quote flip between runs → asserts no accumulation. Fails on master, passes with the fix.
  • All 5525 snapshot tests pass with no output changesissue-3675-index-target (barrel sharing the target path) confirmed preserved.
  • Full local CI gate green: fmt, build, lint, lint:samples, typecheck, test, test:snapshots, orval-tests build (16 clients).
  • Reproduced end-to-end with the reporter's config (tags-split, react-query, workspace, afterAllFilesWrite: prettier, 2 projects): src/index.ts went 4 → 8 → 12 lines across 3 runs, now stable at 4.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented duplicate exports in generated workspace and schema barrel files across repeated code generation runs.
    • Preserved existing exports reliably despite formatting or quote-style changes.
    • Improved merging and ordering of generated barrel exports for consistent output.

…rval-labs#3756)

The workspace barrel and zod verb-schema index deduped re-exports by
matching a single-quote formatted line. An `afterAllFilesWrite` formatter
flipping quote style (e.g. prettier single -> double) defeated the check,
so every export was re-appended on each generation.

Dedup on the bare module specifier instead of the formatted line, via a
shared quote-agnostic readReExportSpecifiers helper. The workspace barrel
and operationSchemas re-export keep append semantics (the barrel can share
its path with the target, orval-labs#3675), while the zod index rebuilds the pure
barrel. All three sites share the one extractor.
Copilot AI review requested due to automatic review settings July 24, 2026 17:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The change adds quote-agnostic barrel export parsing and merging, applies deduplication to schema and workspace index generation, and adds regression coverage for repeated generation with formatter quote changes.

Barrel export idempotency

Layer / File(s) Summary
Barrel parsing and merging
packages/orval/src/utils/barrel.ts, packages/orval/src/utils/index.ts
Adds utilities to parse existing export * specifiers and merge new entries while preserving existing order and removing duplicates.
Generation barrel integration
packages/orval/src/write-specs.ts, packages/orval/src/write-zod-specs.ts
Uses parsed and merged specifier lists when updating schema and workspace barrel files.
Idempotency regression coverage
packages/orval/src/generate-spec.test.ts
Verifies repeated generation across shared projects does not add duplicate exports after quote-style changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

Suggested labels: bug

Suggested reviewers: copilot, soartec-lab, daugvinasr

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: making generated barrels idempotent across formatter runs.
Linked Issues check ✅ Passed The changes address #3756 by deduplicating barrel exports by module specifier and adding regression coverage for repeated generation.
Out of Scope Changes check ✅ Passed No unrelated code changes are apparent; the new utility, write paths, and test all support the idempotency fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/orval/src/write-specs.ts (1)

172-189: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a shared "append missing re-export specifiers" helper.

addOperationSchemasReExport and the workspace barrel block both independently implement "read existing barrel → compute declared specifiers via readReExportSpecifiers → filter to missing → append" inline. Given this PR's goal is eliminating divergent duplicate-detection logic (the root cause of #3756), keeping two separate inline copies risks the same kind of drift recurring.

  • packages/orval/src/write-specs.ts#L172-L189: extract the "declared-set check + conditional append" into a shared helper alongside mergeBarrelSpecifiers in barrel.ts, and call it here.
  • packages/orval/src/write-specs.ts#L805-L833: call the same shared helper here instead of re-deriving declared/toAdd inline.
♻️ Example shared helper (in barrel.ts)
export async function appendMissingReExports(
  filePath: string,
  specifiers: string[],
): Promise<void> {
  if (await fs.pathExists(filePath)) {
    const declared = readReExportSpecifiers(await fs.readFile(filePath, 'utf8'));
    const toAdd = [...new Set(specifiers.filter((s) => !declared.has(s)))];
    if (toAdd.length > 0) {
      await fs.appendFile(
        filePath,
        toAdd.map((s) => `export * from '${s}';\n`).join(''),
      );
    }
  } else {
    await fs.outputFile(
      filePath,
      `${[...new Set(specifiers)].map((s) => `export * from '${s}';`).join('\n')}\n`,
    );
  }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/orval/src/write-specs.ts` around lines 172 - 189, Extract the shared
“append missing re-export specifiers” logic into a helper alongside
mergeBarrelSpecifiers in barrel.ts, reusing readReExportSpecifiers and
deduplicating specifiers. Update packages/orval/src/write-specs.ts lines 172-189
to call the helper, and update lines 805-833 to call the same helper instead of
computing declared/toAdd inline; preserve existing file-creation and append
behavior at both sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/orval/src/write-specs.ts`:
- Around line 172-189: Extract the shared “append missing re-export specifiers”
logic into a helper alongside mergeBarrelSpecifiers in barrel.ts, reusing
readReExportSpecifiers and deduplicating specifiers. Update
packages/orval/src/write-specs.ts lines 172-189 to call the helper, and update
lines 805-833 to call the same helper instead of computing declared/toAdd
inline; preserve existing file-creation and append behavior at both sites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f5e7e17-7bb6-4e1d-aea3-7539491c0fe3

📥 Commits

Reviewing files that changed from the base of the PR and between 4812eb6 and 24ef852.

📒 Files selected for processing (5)
  • packages/orval/src/generate-spec.test.ts
  • packages/orval/src/utils/barrel.ts
  • packages/orval/src/utils/index.ts
  • packages/orval/src/write-specs.ts
  • packages/orval/src/write-zod-specs.ts

@pkg-pr-new

pkg-pr-new Bot commented Jul 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@orval/angular

bun add https://pkg.pr.new/@orval/angular@24ef852

@orval/axios

bun add https://pkg.pr.new/@orval/axios@24ef852

@orval/core

bun add https://pkg.pr.new/@orval/core@24ef852

@orval/effect

bun add https://pkg.pr.new/@orval/effect@24ef852

@orval/fetch

bun add https://pkg.pr.new/@orval/fetch@24ef852

@orval/hono

bun add https://pkg.pr.new/@orval/hono@24ef852

@orval/mcp

bun add https://pkg.pr.new/@orval/mcp@24ef852

@orval/mock

bun add https://pkg.pr.new/@orval/mock@24ef852

orval

bun add https://pkg.pr.new/orval@24ef852

@orval/query

bun add https://pkg.pr.new/@orval/query@24ef852

@orval/solid-start

bun add https://pkg.pr.new/@orval/solid-start@24ef852

@orval/swr

bun add https://pkg.pr.new/@orval/swr@24ef852

@orval/zod

bun add https://pkg.pr.new/@orval/zod@24ef852

commit: 24ef852

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.

Duplicate exports appended to generated index.ts files on repeated Orval generation

3 participants