Skip to content

fix(client-core): resolve runtimeConfigPath relative to generated output#3770

Open
inas-sirhan wants to merge 2 commits intohey-api:mainfrom
inas-sirhan:fix/runtime-config-path
Open

fix(client-core): resolve runtimeConfigPath relative to generated output#3770
inas-sirhan wants to merge 2 commits intohey-api:mainfrom
inas-sirhan:fix/runtime-config-path

Conversation

@inas-sirhan
Copy link
Copy Markdown
Contributor

Fixes #3394

The docs show runtimeConfigPath: './src/hey-api.ts', but this was being copied verbatim into the generated file, so the import ended up wrong unless the user manually wrote the path relative to the generated output directory.

Paths starting with ./ or absolute paths are now resolved from cwd, then made relative to the generated output directory. Bare specifiers (../foo, foo, @scope/pkg) are kept as-is to preserve the current convention and support package imports.

Examples:

output: 'src/client/schema1'
runtimeConfigPath: './src/client/config/schema1.config.ts'
// before: import from './src/client/config/schema1.config.ts'  (broken)
// after:  import from '../config/schema1.config.ts'            (works)
output: 'src/client'
runtimeConfigPath: '../hey-api'
// before: import from '../hey-api'   (works, relative to output)
// after:  import from '../hey-api'   (unchanged, preserved)
runtimeConfigPath: '@my-org/config'
// import from '@my-org/config' (unchanged, npm package)

Added a runtime-config-path scenario to the client snapshot tests across all 7 HTTP clients (angular, axios, fetch, ky, next, nuxt, ofetch).

@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Apr 14, 2026

Reviewed PR #3770. The core fix in resolveRuntimeConfigPath is correct — ./-prefixed and absolute paths are properly resolved from cwd and rewritten relative to the output directory. Left two suggestions: (1) resolve ../-prefixed paths the same way as ./ for consistency, and (2) update the JSDoc on runtimeConfigPath which is now inaccurate.

Task list (5/5 completed)
  • Checkout PR and read diff
  • Analyze core logic change in runtimeConfigPath resolution
  • Review snapshot/test changes
  • Impact analysis: check for stale references or missed call sites
  • Self-critique and submit review

Pullfrog  | View workflow run | Triggered by Pullfrog𝕏

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 14, 2026

@inas-sirhan is attempting to deploy a commit to the Hey API Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Apr 14, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 14, 2026

🦋 Changeset detected

Latest commit: 2efdee9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hey-api/openapi-ts Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Apr 14, 2026

TL;DR — When a user sets runtimeConfigPath to a path like './src/client/config.ts', the value was emitted verbatim into the generated client.gen.ts import, producing a broken path. This PR resolves runtimeConfigPath relative to the generated output directory so the import is always correct.

Key changes

  • Resolve runtimeConfigPath relative to output directory — Adds a resolveRuntimeConfigPath helper in client-core that converts ./-prefixed and absolute paths into correct relative imports from the generated output, while leaving bare specifiers (../foo, @scope/pkg) untouched.
  • Add runtime-config-path snapshot test scenario — Introduces a new test case in clients.test.ts that exercises runtimeConfigPath across all HTTP client plugins, with corresponding snapshot files.

Summary | 116 files | 2 commits | base: mainfix/runtime-config-path


Resolve runtimeConfigPath relative to generated output

Before: runtimeConfigPath: './src/client/config.ts' was copied verbatim into the generated import statement, producing a broken path unless the user manually wrote it relative to the output directory.
After: Paths starting with ./ or absolute paths are resolved from cwd, then made relative to the output directory. Bare specifiers are preserved as-is.

The new resolveRuntimeConfigPath function handles three cases:

Input shape Example Behavior
./-prefixed (cwd-relative) './src/config.ts' Resolved to absolute via process.cwd(), then made relative to output
Absolute path '/home/user/config.ts' Made relative to output directly
Bare specifier '../foo', '@scope/pkg' Returned unchanged
Why are ../-prefixed paths left unchanged? Paths like ../hey-api are already relative — they are treated as relative to the output directory in the generated import, which matches user intent. Only ./-prefixed paths need transformation because they are naturally interpreted as relative to the project root (where the config file lives), not the output directory.

client.ts · clients.test.ts · my-config.ts

Pullfrog  | View workflow run | Triggered by Pullfrog𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Important

The resolution logic has an asymmetry that could confuse users, and the JSDoc on runtimeConfigPath is now inaccurate. Worth addressing before merge.

The fix itself is correct — ./-prefixed paths are resolved from cwd and made relative to the output directory, which matches what the docs examples have always shown. Backward compatibility for ../ and bare specifiers is preserved. Snapshot math checks out across all 7 clients.

JSDoc on runtimeConfigPath (types.ts:48): The JSDoc says "Relative path to the runtime configuration file" — this is now inaccurate. After this PR, ./-prefixed paths are resolved from cwd, and absolute paths are also supported. Consider updating to something like: "Path to the runtime configuration file. Paths starting with ./ or absolute paths are resolved relative to cwd and rewritten to be relative to the generated output. Other paths (../foo, @scope/pkg, foo) are used as-is."

Task list (5/5 completed)
  • Checkout PR and read diff
  • Analyze core logic change in runtimeConfigPath resolution
  • Review snapshot/test changes
  • Impact analysis: check for stale references or missed call sites
  • Self-critique and submit review

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run𝕏

Comment on lines +18 to +19
if (!path.isAbsolute(runtimeConfigPath) && !runtimeConfigPath.startsWith('./')) {
return runtimeConfigPath;
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.

The classification heuristic creates an asymmetry that isn't obvious to users:

  • ./src/config.ts → resolved from cwd, then made relative to output
  • ../config.ts → passed through verbatim (treated as already relative to output)

This means ./ and ../ have fundamentally different semantics even though they're both conventional relative-path prefixes. A user writing runtimeConfigPath: '../shared/config.ts' probably intends it relative to cwd (same as ./), not relative to the output directory.

Consider resolving all dotted relative paths (starting with ./ or ../) from cwd, and only passing through bare specifiers (no leading .). That would make the behavior consistent and match how output.path itself is resolved. The existing openapi-ts-next example ('../hey-api') would need its path updated, but it would behave correctly regardless.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

^ @inas-sirhan thoughts?

const absoluteInputPath = path.isAbsolute(runtimeConfigPath)
? runtimeConfigPath
: path.resolve(process.cwd(), runtimeConfigPath);
const relative = path.relative(outputPath, absoluteInputPath).split(path.sep).join('/');
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.

Normalizing path.sep to / is the right call for generated imports.

Paths starting with './' or absolute paths are now resolved from the current
working directory and made relative to the generated output directory, so the
resulting import path is correct regardless of where the user's runtime config
file lives.

Paths without a './' prefix (e.g. '../my-config', 'my-config', '@scope/pkg')
are kept as-is to preserve the existing convention of treating them as
relative to the generated output or as bare module specifiers.

Closes hey-api#3394
@inas-sirhan inas-sirhan force-pushed the fix/runtime-config-path branch from f97ee82 to 2efdee9 Compare April 14, 2026 20:37
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 40.00%. Comparing base (7d501f1) to head (2efdee9).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3770      +/-   ##
==========================================
- Coverage   40.01%   40.00%   -0.02%     
==========================================
  Files         521      521              
  Lines       19300    19309       +9     
  Branches     5756     5767      +11     
==========================================
+ Hits         7723     7724       +1     
- Misses       9374     9379       +5     
- Partials     2203     2206       +3     
Flag Coverage Δ
unittests 40.00% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 14, 2026

Open in StackBlitz

@hey-api/codegen-core

npm i https://pkg.pr.new/@hey-api/codegen-core@3770

@hey-api/json-schema-ref-parser

npm i https://pkg.pr.new/@hey-api/json-schema-ref-parser@3770

@hey-api/nuxt

npm i https://pkg.pr.new/@hey-api/nuxt@3770

@hey-api/openapi-ts

npm i https://pkg.pr.new/@hey-api/openapi-ts@3770

@hey-api/shared

npm i https://pkg.pr.new/@hey-api/shared@3770

@hey-api/spec-types

npm i https://pkg.pr.new/@hey-api/spec-types@3770

@hey-api/types

npm i https://pkg.pr.new/@hey-api/types@3770

@hey-api/vite-plugin

npm i https://pkg.pr.new/@hey-api/vite-plugin@3770

commit: 2efdee9

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented Apr 15, 2026

No action needed — the reviewer's comment (@inas-sirhan thoughts?) is directed at the PR author, asking them to weigh in on the path-resolution asymmetry raised in my earlier review. Waiting on the contributor's response.

Task list (1/1 completed)
  • Assess whether any action is needed based on reviewer comment

Pullfrog  | View workflow run | via Pullfrog𝕏

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented Apr 15, 2026

@inas-sirhan how does it handle absolute paths and relative paths if the config is in a nested folder (i.e., not in root)? Is it the same how output.path works?

@inas-sirhan
Copy link
Copy Markdown
Contributor Author

same as output.path, yep. both use path.resolve(process.cwd(), ...) so:

  • absolute paths: used directly, then made relative to output
  • relative paths: resolved from process.cwd() (where the user runs the cli from), so if they run it from a nested folder, paths are relative to that folder

for consistency i checked packages/openapi-ts/src/config/resolve.ts and that's exactly what output.path does on line 27:

output.path = path.resolve(process.cwd(), output.path);

so if someone's config is in apps/api/openapi-ts.config.ts and they run openapi-ts from apps/api/, both output.path: './src/client' and runtimeConfigPath: './src/hey-api.ts' resolve from apps/api/. @mrlubos

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

Labels

size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

runtimeConfigPath is copied verbatim instead of being resolved relative to generated output

2 participants