Skip to content

Commit 7c3152c

Browse files
committed
feat: find and use import alias
1 parent 1c5d015 commit 7c3152c

6 files changed

Lines changed: 56 additions & 11 deletions

File tree

integration-tests/typescript-nextjs/src/generated/api.github.com.yaml/app/route.ts

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/openapi-code-generator/src/core/loaders/tsconfig.loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212

1313
export type CompilerOptions = Pick<
1414
TsCompilerOptions,
15-
"exactOptionalPropertyTypes"
15+
"exactOptionalPropertyTypes" | "paths"
1616
>
1717

1818
export async function loadTsConfigCompilerOptions(

packages/openapi-code-generator/src/core/schemas/tsconfig.schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const tsconfigSchema = z.object({
3535
noPropertyAccessFromIndexSignature: z.boolean(),
3636
allowUnusedLabels: z.boolean(),
3737
allowUnreachableCode: z.boolean(),
38+
paths: z.record(z.array(z.string())),
3839
})
3940
.partial(),
4041
})

packages/openapi-code-generator/src/core/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ export function isDefined<T>(it: T | undefined): it is T {
66
return it !== undefined
77
}
88

9+
export function isTruthy<T>(it: T | undefined | null | "" | 0): it is T {
10+
return Boolean(it)
11+
}
12+
913
export function hasSingleElement<T>(it: T[]): it is [T] {
1014
return it.length === 1
1115
}

packages/openapi-code-generator/src/typescript/common/import-builder.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ export class ImportBuilder {
55
private readonly imports: Record<string, Set<string>> = {}
66
private readonly importAll: Record<string, string> = {}
77

8-
constructor(private readonly unit?: {filename: string}) {}
8+
constructor(
9+
private readonly unit?: {filename: string},
10+
private readonly importAlias?: string,
11+
) {}
912

1013
from(from: string) {
1114
return {
@@ -76,7 +79,7 @@ export class ImportBuilder {
7679

7780
private add(name: string, from: string, isAll: boolean): void {
7881
// biome-ignore lint/style/noParameterAssign: <explanation>
79-
from = ImportBuilder.normalizeFrom(from, this.unit?.filename)
82+
from = this.normalizeFrom(from, this.unit?.filename)
8083
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
8184
const imports = (this.imports[from] =
8285
this.imports[from] ?? new Set<string>())
@@ -88,14 +91,18 @@ export class ImportBuilder {
8891
}
8992
}
9093

91-
public static normalizeFrom(from: string, filename?: string) {
94+
public normalizeFrom(from: string, filename?: string) {
9295
if (from.endsWith(".ts")) {
9396
// biome-ignore lint/style/noParameterAssign: <explanation>
9497
from = from.substring(0, from.length - ".ts".length)
9598
}
9699

97100
// TODO: does this work on windows?
98101
if (filename && from.startsWith("./")) {
102+
if (this.importAlias) {
103+
return this.importAlias + from.split(path.sep).slice(1).join(path.sep)
104+
}
105+
99106
const unitDirname = path.dirname(filename)
100107
const fromDirname = path.dirname(from)
101108

packages/openapi-code-generator/src/typescript/typescript-nextjs/typescript-nextjs.generator.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import {
1111
VariableDeclarationKind,
1212
} from "ts-morph"
1313
import type {Input} from "../../core/input"
14+
import type {CompilerOptions} from "../../core/loaders/tsconfig.loader"
1415
import type {
1516
IRModelObject,
1617
IROperation,
1718
IRParameter,
1819
} from "../../core/openapi-types-normalized"
20+
import {isTruthy} from "../../core/utils"
1921
import {
2022
type HttpMethod,
2123
isDefined,
@@ -321,6 +323,7 @@ ${routes.join("\n\n")}
321323
export class NextJSAppRouterBuilder implements ICompilable {
322324
constructor(
323325
public readonly filename: string,
326+
private readonly imports: ImportBuilder,
324327
private readonly companionFilename: string,
325328
private readonly sourceFile: SourceFile,
326329
) {}
@@ -394,7 +397,7 @@ export class NextJSAppRouterBuilder implements ICompilable {
394397
toCompilationUnit(): CompilationUnit {
395398
// Reconcile imports - attempt to find an existing one and replace it with correct one
396399
const imports = this.sourceFile.getImportDeclarations()
397-
const from = ImportBuilder.normalizeFrom(
400+
const from = this.imports.normalizeFrom(
398401
"./" + this.companionFilename,
399402
"./" + this.filename,
400403
)
@@ -418,26 +421,43 @@ export class NextJSAppRouterBuilder implements ICompilable {
418421

419422
return new CompilationUnit(
420423
this.filename,
421-
new ImportBuilder(),
424+
this.imports,
422425
this.toString(),
423426
false,
424427
)
425428
}
426429
}
427430

431+
function findImportAlias(dest: string, compilerOptions: CompilerOptions) {
432+
const relative = `./${path.relative(process.cwd(), dest)}/*`
433+
434+
const alias = Object.entries(compilerOptions.paths || {}).find(([, paths]) =>
435+
paths.includes(relative),
436+
)
437+
438+
return alias ? alias[0].replace("*", "") : undefined
439+
}
440+
428441
export async function generateTypescriptNextJS(
429442
config: OpenapiTypescriptGeneratorConfig,
430443
): Promise<void> {
431444
const {input, emitter, allowAny} = config
432445

446+
const importAlias = findImportAlias(
447+
config.emitter.config.destinationDirectory,
448+
config.compilerOptions,
449+
)
450+
433451
// biome-ignore lint/complexity/useLiteralKeys: <explanation>
434452
const subDirectory = process.env["OPENAPI_INTEGRATION_TESTS"]
435453
? path.basename(config.input.loader.entryPointKey)
436454
: ""
437455

438-
const appDirectory = ["./app", subDirectory].filter(isDefined).join(path.sep)
439-
const generatedDirectory = ["./generated", subDirectory]
440-
.filter(isDefined)
456+
const appDirectory = [".", "app", subDirectory]
457+
.filter(isTruthy)
458+
.join(path.sep)
459+
const generatedDirectory = [".", "generated", subDirectory]
460+
.filter(isTruthy)
441461
.join(path.sep)
442462

443463
const rootTypeBuilder = await TypeBuilder.fromInput(
@@ -464,7 +484,7 @@ export async function generateTypescriptNextJS(
464484
routeToNextJSFilepath(group.name),
465485
)
466486

467-
const imports = new ImportBuilder({filename})
487+
const imports = new ImportBuilder({filename}, importAlias)
468488

469489
const routerBuilder = new ServerRouterBuilder(
470490
filename,
@@ -500,6 +520,7 @@ export async function generateTypescriptNextJS(
500520

501521
const nextJSAppRouterBuilder = new NextJSAppRouterBuilder(
502522
nextJsAppRouterPath,
523+
imports,
503524
filename,
504525
sourceFile,
505526
)
@@ -520,7 +541,10 @@ export async function generateTypescriptNextJS(
520541
const clientOutputPath = [generatedDirectory, "clients", "client.ts"].join(
521542
path.sep,
522543
)
523-
const clientImportBuilder = new ImportBuilder({filename: clientOutputPath})
544+
const clientImportBuilder = new ImportBuilder(
545+
{filename: clientOutputPath},
546+
importAlias,
547+
)
524548

525549
const fetchClientBuilder = new TypescriptFetchClientBuilder(
526550
clientOutputPath,

0 commit comments

Comments
 (0)