Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ jobs:
- name: Run tsc in nextjs-app
run: pnpm --filter nextjs-app test:generated

- name: Run next build in nextjs-app
run: pnpm --filter nextjs-app build

- name: Run tsc in tanstack-router-app
run: pnpm --filter tanstack-router-app test:generated

Expand Down
3 changes: 1 addition & 2 deletions examples/nextjs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
"dev:mock": "prism mock ../petstore.yaml --dynamic",
"build": "next build",
"start": "next start",
"lint": "next lint",
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml --format=biome --lint=biome",
"test:generated": "tsc -p ./tsconfig.json --noEmit"
},
"dependencies": {
"@hey-api/client-fetch": "^0.6.0",
"@tanstack/react-query": "^5.59.13",
"@tanstack/react-query-devtools": "^5.32.1",
"next": "^15.5.16",
"next": "^16.2.10",
"react": "^18",
"react-dom": "^18"
},
Expand Down
10 changes: 8 additions & 2 deletions examples/nextjs-app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
Expand All @@ -22,6 +22,12 @@
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules"]
}
96 changes: 52 additions & 44 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 37 additions & 4 deletions src/generate.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { writeFile } from "node:fs/promises";
import { readdirSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { type UserConfig, createClient } from "@hey-api/openapi-ts";
import type { LimitedUserConfig } from "./cli.mjs";
Expand All @@ -11,6 +12,27 @@ import { createSource } from "./createSource.mjs";
import { formatOutput, processOutput } from "./format.mjs";
import { print } from "./print.mjs";

// openapi-ts's own tsconfig auto-detection walks up from its *install*
// location, which in this monorepo reaches this repo's own root tsconfig
// (NodeNext) instead of the caller's project config. Anchoring the search
// at cwd instead finds the tsconfig that actually applies to the generated
// output, matching the resolution the caller's own `tsc` will use.
export function findNearestTsConfigPath(startDir: string): string | undefined {
let dir = startDir;
while (true) {
const candidates = readdirSync(dir).filter(
(file) => file.startsWith("tsconfig") && file.endsWith(".json"),
);
if (candidates.length > 0) {
candidates.sort((a) => (a === "tsconfig.json" ? -1 : 1));
return path.join(dir, candidates[0]);
}
const parent = path.dirname(dir);
if (parent === dir) return undefined;
dir = parent;
}
}

export async function generate(options: LimitedUserConfig, version: string) {
const openApiOutputPath = buildRequestsOutputPath(options.output);
const formattedOptions = formatOptions(options);
Expand Down Expand Up @@ -61,14 +83,25 @@ export async function generate(options: LimitedUserConfig, version: string) {
const config: UserConfig = {
dryRun: false,
input: formattedOptions.input,
output: openApiOutputPath,
output: {
path: openApiOutputPath,
tsConfigPath: findNearestTsConfigPath(process.cwd()) ?? null,
},
plugins,
};
await createClient(config);

// Generate backward-compatible services.gen.ts shim
// client.gen.ts has the `client` instance; sdk.gen.ts has SDK functions
const shimContent = `// This file is auto-generated for backward compatibility\nexport * from './client.gen.js';\nexport * from './sdk.gen.js';\n`;
// client.gen.ts has the `client` instance; sdk.gen.ts has SDK functions.
// Mirror whatever extension convention openapi-ts used for its own
// cross-file imports (e.g. `.js` for NodeNext, none for bundler resolution).
const sdkGenContent = await readFile(
path.join(openApiOutputPath, "sdk.gen.ts"),
"utf-8",
);
const importExtension =
sdkGenContent.match(/from ['"]\.\/client\.gen(\.\S*)?['"]/)?.[1] ?? "";
const shimContent = `// This file is auto-generated for backward compatibility\nexport * from './client.gen${importExtension}';\nexport * from './sdk.gen${importExtension}';\n`;
await writeFile(path.join(openApiOutputPath, "services.gen.ts"), shimContent);

const source = await createSource({
Expand Down
Loading
Loading