diff --git a/.changeset/deep-houses-exist.md b/.changeset/deep-houses-exist.md new file mode 100644 index 0000000000..ed9dff714a --- /dev/null +++ b/.changeset/deep-houses-exist.md @@ -0,0 +1,5 @@ +--- +"@hey-api/shared": patch +--- + +**types**: update project meta types diff --git a/.changeset/empty-stars-crash.md b/.changeset/empty-stars-crash.md new file mode 100644 index 0000000000..4338800d23 --- /dev/null +++ b/.changeset/empty-stars-crash.md @@ -0,0 +1,5 @@ +--- +"@hey-api/codegen-core": minor +--- + +**types**: rename `ProjectRenderMeta` to `ProjectMeta` and key it by language diff --git a/packages/codegen-core/src/__tests__/exports.test.ts b/packages/codegen-core/src/__tests__/exports.test.ts index 4c74f76d23..9a6a49849c 100644 --- a/packages/codegen-core/src/__tests__/exports.test.ts +++ b/packages/codegen-core/src/__tests__/exports.test.ts @@ -53,7 +53,7 @@ export type _TypeExports = [ index.NodeScope, index.Output, index.Project, - index.ProjectRenderMeta, + index.ProjectMeta, index.Ref, index.Refs, index.RenderContext, diff --git a/packages/codegen-core/src/__tests__/project.test.ts b/packages/codegen-core/src/__tests__/project.test.ts index b7dc6c11fc..0ecf91f8dd 100644 --- a/packages/codegen-core/src/__tests__/project.test.ts +++ b/packages/codegen-core/src/__tests__/project.test.ts @@ -44,16 +44,12 @@ describe('Project', () => { it('passes correct ctx to renderer.render()', () => { const p = makeProject(); - p.render({ hello: true }); + p.render(); const file = [...p.files.registered()][0]!; const renderer = file.renderer!; - expect(renderer.render).toHaveBeenCalledWith({ - file, - meta: { hello: true }, - project: p, - }); + expect(renderer.render).toHaveBeenCalledWith({ file, project: p }); }); }); diff --git a/packages/codegen-core/src/extensions.ts b/packages/codegen-core/src/extensions.ts index 092b1220f9..cb4ee0a157 100644 --- a/packages/codegen-core/src/extensions.ts +++ b/packages/codegen-core/src/extensions.ts @@ -1,14 +1,15 @@ +import type { Language } from './languages/types'; + /** - * Arbitrary metadata passed to the project's render function. + * Arbitrary project metadata. * * Implementers should extend this interface for their own needs. */ -export interface IProjectRenderMeta { - [key: string]: unknown; -} +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface IProjectMeta extends Partial> {} /** - * Additional metadata about the symbol. + * Arbitrary symbol metadata. * * Implementers should extend this interface for their own needs. */ diff --git a/packages/codegen-core/src/index.ts b/packages/codegen-core/src/index.ts index 0617cc1541..a3dd95bd7a 100644 --- a/packages/codegen-core/src/index.ts +++ b/packages/codegen-core/src/index.ts @@ -3,10 +3,7 @@ export { nodeBrand, symbolBrand } from './brands'; export { detectInteractiveSession } from './config/interactive'; export { loadConfigFile } from './config/load'; export { mergeConfigs } from './config/merge'; -export type { - IProjectRenderMeta as ProjectRenderMeta, - ISymbolMeta as SymbolMeta, -} from './extensions'; +export type { IProjectMeta as ProjectMeta, ISymbolMeta as SymbolMeta } from './extensions'; export { File } from './files/file'; export type { IFileIn as FileIn } from './files/types'; export { isNode, isNodeRef, isSymbol, isSymbolRef } from './guards'; diff --git a/packages/codegen-core/src/planner/analyzer.ts b/packages/codegen-core/src/planner/analyzer.ts index aa47fc3764..637554c351 100644 --- a/packages/codegen-core/src/planner/analyzer.ts +++ b/packages/codegen-core/src/planner/analyzer.ts @@ -1,3 +1,4 @@ +import type { IProjectMeta } from '../extensions'; import { isNodeRef, isSymbolRef } from '../guards'; import type { INode, NodeRelationship } from '../nodes/node'; import { fromRef, isRef, ref } from '../refs/refs'; @@ -15,12 +16,15 @@ export class AnalysisContext implements IAnalysisContext { */ private _parentStack: Array = []; + /** Arbitrary project metadata. */ + meta: IProjectMeta; scope: Scope; scopes: Scope = createScope(); symbol?: Symbol; - constructor(node: INode) { + constructor(node: INode, meta: IProjectMeta) { this._parentStack.push(node); + this.meta = meta; this.scope = this.scopes; this.symbol = node.symbol; } @@ -149,14 +153,19 @@ export class AnalysisContext implements IAnalysisContext { } export class Analyzer { + private readonly meta: IProjectMeta; private nodeCache = new WeakMap(); + constructor(meta: IProjectMeta) { + this.meta = meta; + } + analyzeNode(node: INode): AnalysisContext { const cached = this.nodeCache.get(node); if (cached) return cached; node.root = true; - const ctx = new AnalysisContext(node); + const ctx = new AnalysisContext(node, this.meta); node.analyze(ctx); this.nodeCache.set(node, ctx); diff --git a/packages/codegen-core/src/planner/planner.ts b/packages/codegen-core/src/planner/planner.ts index 90fd062650..854066a65a 100644 --- a/packages/codegen-core/src/planner/planner.ts +++ b/packages/codegen-core/src/planner/planner.ts @@ -1,7 +1,6 @@ import path from 'node:path'; import type { ExportModule, ImportModule } from '../bindings'; -import type { IProjectRenderMeta } from '../extensions'; import type { File } from '../files/file'; import type { INode } from '../nodes/node'; import { canDeclarationsShareIdentifier } from '../project/namespace'; @@ -18,22 +17,23 @@ import { createScope, registerName } from './scope'; const isTypeOnlyKind = (kind: SymbolKind) => kind === 'type' || kind === 'interface'; export class Planner { - private readonly analyzer = new Analyzer(); + private readonly analyzer: Analyzer; private readonly cacheResolvedNames = new Set(); private readonly project: IProject; constructor(project: IProject) { + this.analyzer = new Analyzer(project.meta); this.project = project; } /** * Executes the planning phase for the project. */ - plan(meta?: IProjectRenderMeta) { + plan() { this.cacheResolvedNames.clear(); this.allocateFiles(); this.assignLocalNames(); - this.resolveFilePaths(meta); + this.resolveFilePaths(); this.planExports(); this.planImports(); } @@ -112,7 +112,7 @@ export class Planner { * * Resolves final paths relative to the project's root directory. */ - private resolveFilePaths(meta?: IProjectRenderMeta): void { + private resolveFilePaths(): void { for (const file of this.project.files.registered()) { if (file.external) { file.setFinalPath(file.logicalFilePath); @@ -124,7 +124,7 @@ export class Planner { if (finalPath) { file.setFinalPath(path.resolve(this.project.root, finalPath)); } - const ctx: RenderContext = { file, meta, project: this.project }; + const ctx: RenderContext = { file, project: this.project }; const renderer = this.project.renderers.find((r) => r.supports(ctx)); if (renderer) file.setRenderer(renderer); } diff --git a/packages/codegen-core/src/planner/types.ts b/packages/codegen-core/src/planner/types.ts index 94e75a3c33..1d0b2452c9 100644 --- a/packages/codegen-core/src/planner/types.ts +++ b/packages/codegen-core/src/planner/types.ts @@ -1,3 +1,4 @@ +import type { IProjectMeta } from '../extensions'; import type { Ref } from '../refs/types'; import type { Symbol } from '../symbols/symbol'; import type { NameScopes, Scope } from './scope'; @@ -20,6 +21,8 @@ export interface IAnalysisContext { injectChildren(input: Input): void; /** Get local names in the current scope. */ localNames(scope: Scope): NameScopes; + /** Arbitrary project metadata. */ + meta: IProjectMeta; /** Pop the current local scope. */ popScope(): void; /** Push a new local scope. */ diff --git a/packages/codegen-core/src/project/project.ts b/packages/codegen-core/src/project/project.ts index 1ff95558e4..07b8d74b23 100644 --- a/packages/codegen-core/src/project/project.ts +++ b/packages/codegen-core/src/project/project.ts @@ -1,6 +1,6 @@ import path from 'node:path'; -import type { IProjectRenderMeta } from '../extensions'; +import type { IProjectMeta } from '../extensions'; import { FileRegistry } from '../files/registry'; import { defaultExtensions } from '../languages/extensions'; import { defaultModuleEntryNames } from '../languages/modules'; @@ -19,6 +19,7 @@ export class Project implements IProject { private _isPlanned = false; readonly files: FileRegistry; + readonly meta: IProjectMeta; readonly nodes = new NodeRegistry(); readonly symbols = new SymbolRegistry(); @@ -42,8 +43,9 @@ export class Project implements IProject { | 'nameConflictResolvers' | 'renderers' > & - Pick, + Pick & { meta?: IProjectMeta }, ) { + this.meta = args.meta ?? {}; const fileName = args.fileName; this.defaultFileName = args.defaultFileName ?? 'main'; this.defaultNameConflictResolver = @@ -66,18 +68,18 @@ export class Project implements IProject { this.root = path.resolve(args.root).replace(/[/\\]+$/, ''); } - plan(meta?: IProjectRenderMeta): void { + plan(): void { if (this._isPlanned) return; - new Planner(this).plan(meta); + new Planner(this).plan(); this._isPlanned = true; } - render(meta?: IProjectRenderMeta): ReadonlyArray { - if (!this._isPlanned) this.plan(meta); + render(): ReadonlyArray { + if (!this._isPlanned) this.plan(); const files: Array = []; for (const file of this.files.registered()) { if (!file.external && file.finalPath && file.renderer) { - const content = file.renderer.render({ file, meta, project: this }); + const content = file.renderer.render({ file, project: this }); files.push({ content, path: file.finalPath }); } } diff --git a/packages/codegen-core/src/project/types.ts b/packages/codegen-core/src/project/types.ts index e3a1863af0..ee5604cd51 100644 --- a/packages/codegen-core/src/project/types.ts +++ b/packages/codegen-core/src/project/types.ts @@ -1,4 +1,4 @@ -import type { IProjectRenderMeta } from '../extensions'; +import type { IProjectMeta } from '../extensions'; import type { IFileRegistry } from '../files/types'; import type { Extensions, ModuleEntryNames, NameConflictResolvers } from '../languages/types'; import type { INodeRegistry } from '../nodes/types'; @@ -42,6 +42,8 @@ export interface IProject { readonly fileName?: (name: string) => string; /** Centralized file registry for the project. */ readonly files: IFileRegistry; + /** Arbitrary project metadata. */ + readonly meta: IProjectMeta; /** * Map of module entry names for each language. * @@ -74,7 +76,7 @@ export interface IProject { * @param meta Arbitrary metadata. * @returns void */ - plan(meta?: IProjectRenderMeta): void; + plan(meta?: IProjectMeta): void; /** * Produces output representations for all files in the project. * @@ -83,7 +85,7 @@ export interface IProject { * @example * project.render().forEach(output => writeFile(output)); */ - render(meta?: IProjectRenderMeta): ReadonlyArray; + render(meta?: IProjectMeta): ReadonlyArray; /** * List of available renderers. * diff --git a/packages/codegen-core/src/renderer.ts b/packages/codegen-core/src/renderer.ts index ceee414c04..718e5c2f54 100644 --- a/packages/codegen-core/src/renderer.ts +++ b/packages/codegen-core/src/renderer.ts @@ -1,4 +1,3 @@ -import type { IProjectRenderMeta } from './extensions'; import type { File } from './files/file'; import type { INode } from './nodes/node'; import type { IProject } from './project/types'; @@ -8,10 +7,6 @@ export interface RenderContext { * The current file. */ file: File; - /** - * Arbitrary metadata. - */ - meta?: IProjectRenderMeta; /** * The project the file belongs to. */ diff --git a/packages/openapi-python/src/createClient.ts b/packages/openapi-python/src/createClient.ts index 213a0cb385..5d5777b508 100644 --- a/packages/openapi-python/src/createClient.ts +++ b/packages/openapi-python/src/createClient.ts @@ -138,6 +138,11 @@ export async function createClient({ } return name === '__init__' || name.endsWith(suffix) ? name : `${name}${suffix}`; }, + meta: { + python: { + version: config.output.pythonVersion, + }, + }, nameConflictResolvers: config.output.nameConflictResolver ? { python: config.output.nameConflictResolver, diff --git a/packages/openapi-python/src/index.ts b/packages/openapi-python/src/index.ts index 10e4317e9b..6e05de4583 100644 --- a/packages/openapi-python/src/index.ts +++ b/packages/openapi-python/src/index.ts @@ -5,15 +5,10 @@ import '@hey-api/codegen-core'; import '@hey-api/shared'; declare module '@hey-api/codegen-core' { - interface ProjectRenderMeta { - /** - * If specified, this will be the file extension used when importing - * other modules. By default, we don't add a file extension and let the - * runtime resolve it. - * - * @default null - */ - importFileExtension?: AnyString | null; + interface ProjectMeta { + python?: { + version: PythonVersion; + }; } interface SymbolMeta { @@ -61,6 +56,7 @@ import colors from 'ansi-colors'; // @ts-expect-error import colorSupport from 'color-support'; +import type { PythonVersion } from './config/output/types'; import type { UserConfig } from './config/types'; import type { HeyApiClientHttpxPlugin } from './plugins/@hey-api/client-httpx'; import type { HeyApiSdkPlugin } from './plugins/@hey-api/sdk'; diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 951e395741..358407079a 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -5,7 +5,7 @@ import '@hey-api/codegen-core'; import '@hey-api/shared'; declare module '@hey-api/codegen-core' { - interface ProjectRenderMeta { + interface ProjectMeta { /** * If specified, this will be the file extension used when importing * other modules. By default, we don't add a file extension and let the diff --git a/packages/shared/src/config/output/types.ts b/packages/shared/src/config/output/types.ts index 5576d2f046..6c6beb4345 100644 --- a/packages/shared/src/config/output/types.ts +++ b/packages/shared/src/config/output/types.ts @@ -3,7 +3,7 @@ import type { MaybeArray, MaybeFunc } from '@hey-api/types'; export type OutputHeader = MaybeFunc< ( - ctx: Pick & + ctx: Pick & Pick, 'file'> & { /** The default header value. */ defaultValue: ReadonlyArray;