Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(openapi): build script to setup ESM/CJS module exports + various fixes #10

Closed
wants to merge 4 commits into from
Closed
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
12 changes: 12 additions & 0 deletions config-node.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
additionalProperties:
npmName: "openai"
npmVersion: "3.3.0"
supportsES6: true
withNodeImports: false
files:
tsc-multi.mustache:
templateType: SupportingFiles
destinationFilename: tsc-multi.json
stream.mustache:
templateType: SupportingFiles
destinationFilename: stream.ts
6 changes: 6 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ servers:
tags:
- name: OpenAI
description: The OpenAI REST API
security:
- apiKeyAuth: []
paths:
/engines:
get:
Expand Down Expand Up @@ -3562,6 +3564,10 @@ components:
- created_at
- level
- message
securitySchemes:
bearer:
type: http
scheme: bearer

x-oaiMeta:
groups:
Expand Down
8 changes: 6 additions & 2 deletions scripts/generate_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,14 @@ def generate_sanitized_spec(sanitized_spec_path):
def generate_sdk(sanitized_spec_path, sdk_type, output_path):
"""Use openapi-generator to generate the SDK."""
if sdk_type == "node":
sanitized_spec_path = sanitized_spec_path.replace(" ", "\\ ")
output_path = output_path.replace(" ", "\\ ")
template_override_path = os.path.join(os.path.dirname(
__file__), "../sdk-template-overrides/typescript-axios")
__file__), "../sdk-template-overrides/typescript-axios").replace(" ", "\\ ")
configuration_path = os.path.join(os.path.dirname(
__file__), "../config-node.yaml").replace(" ", "\\ ")
os.system(
f"openapi-generator generate -i {sanitized_spec_path} -g typescript-axios -o {output_path} -p supportsES6=true -t {template_override_path}")
f"openapi-generator generate -i {sanitized_spec_path} -g typescript-axios -o {output_path} -t {template_override_path} -c {configuration_path}")
else:
print(f"Unsupported SDK type {sdk_type}, skipping SDK generation")

Expand Down
39 changes: 33 additions & 6 deletions sdk-template-overrides/typescript-axios/apiInner.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
/* eslint-disable */
{{>licenseInfo}}

import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import { Configuration } from '{{apiRelativeToRoot}}configuration';
import type { Configuration } from '{{apiRelativeToRoot}}configuration';
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
{{#withNodeImports}}
// URLSearchParams not necessarily used
// @ts-ignore
import { URL, URLSearchParams } from 'url';
{{#multipartFormData}}
import FormData from 'form-data'
{{/multipartFormData}}
{{/withNodeImports}}
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '{{apiRelativeToRoot}}common';
Expand Down Expand Up @@ -138,12 +147,16 @@ export const {{classname}}AxiosParamCreator = function (configuration?: Configur
}
{{/isArray}}
{{^isArray}}
if ({{paramName}} !== undefined && {{paramName}} !== null) {
{{! `val == null` covers for both `null` and `undefined`}}
if ({{paramName}} != null) {
{{#isString}}
localVarHeaderParameter['{{baseName}}'] = String({{paramName}});
{{/isString}}
{{^isString}}
localVarHeaderParameter['{{baseName}}'] = String(JSON.stringify({{paramName}}));
{{! isString is falsy also for $ref that defines a string or enum type}}
localVarHeaderParameter['{{baseName}}'] = typeof {{paramName}} === 'string'
? {{paramName}}
: JSON.stringify({{paramName}});
{{/isString}}
}
{{/isArray}}
Expand Down Expand Up @@ -184,8 +197,8 @@ export const {{classname}}AxiosParamCreator = function (configuration?: Configur
{{/bodyParam}}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
{{! Edited by OpenAI, line 188 }}
localVarRequestOptions.headers = {...localVarHeaderParameter,{{#vendorExtensions}}{{#multipartFormData}} ...localVarFormParams.getHeaders(),{{/multipartFormData}}{{/vendorExtensions}} ...headersFromBaseOptions, ...options.headers};
{{! Edited by OpenAI, line 201 }}
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions,{{#vendorExtensions}}{{#multipartFormData}} ...(localVarFormParams as any).getHeaders?.(),{{/multipartFormData}}{{/vendorExtensions}} ...options.headers};
{{#hasFormParams}}
localVarRequestOptions.data = localVarFormParams{{#vendorExtensions}}{{^multipartFormData}}.toString(){{/multipartFormData}}{{/vendorExtensions}};
{{/hasFormParams}}
Expand Down Expand Up @@ -245,16 +258,30 @@ export const {{classname}}Factory = function (configuration?: Configuration, bas
{{#summary}}
* @summary {{&summary}}
{{/summary}}
{{#useSingleRequestParameter}}
{{#allParams.0}}
* @param {{=<% %>=}}{<%& classname %><%& operationIdCamelCase %>Request}<%={{ }}=%> requestParameters Request parameters.
{{/allParams.0}}
{{/useSingleRequestParameter}}
{{^useSingleRequestParameter}}
{{#allParams}}
* @param {{=<% %>=}}{<%&dataType%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}}
{{/allParams}}
{{/useSingleRequestParameter}}
* @param {*} [options] Override http request option.{{#isDeprecated}}
* @deprecated{{/isDeprecated}}
* @throws {RequiredError}
*/
{{#useSingleRequestParameter}}
{{nickname}}({{#allParams.0}}requestParameters: {{classname}}{{operationIdCamelCase}}Request{{^hasRequiredParams}} = {}{{/hasRequiredParams}}, {{/allParams.0}}options?: AxiosRequestConfig): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}> {
return localVarFp.{{nickname}}({{#allParams.0}}{{#allParams}}requestParameters.{{paramName}}, {{/allParams}}{{/allParams.0}}options).then((request) => request(axios, basePath));
},
{{/useSingleRequestParameter}}
{{^useSingleRequestParameter}}
{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: any): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}> {
return localVarFp.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options).then((request) => request(axios, basePath));
},
{{/useSingleRequestParameter}}
{{/operation}}
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
/* eslint-disable */
{{>licenseInfo}}

{{! Edited by OpenAI, line 6 }}
const packageJson = require("../package.json");
{{! Edited by OpenAI, line 6-7 }}
import FormData from "form-data";
import { LIB_VERSION } from "./version";

export interface ConfigurationParameters {
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
Expand Down Expand Up @@ -92,15 +93,15 @@ export class Configuration {
this.baseOptions = {};
}
this.baseOptions.headers = {
'User-Agent': `OpenAI/NodeJS/${packageJson.version}`,
'User-Agent': `OpenAI/NodeJS/${LIB_VERSION}`,
'Authorization': `Bearer ${this.apiKey}`,
...this.baseOptions.headers,
}
if (this.organization) {
this.baseOptions.headers['OpenAI-Organization'] = this.organization;
}
if (!this.formDataCtor) {
this.formDataCtor = require("form-data");
this.formDataCtor = FormData;
}
}

Expand Down
68 changes: 68 additions & 0 deletions sdk-template-overrides/typescript-axios/package.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "{{npmName}}",
"version": "{{npmVersion}}",
"description": "OpenAPI client for {{npmName}}",
"author": "OpenAPI-Generator Contributors",
"repository": {
"type": "git",
"url": "https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}.git"
},
"keywords": [
"axios",
"typescript",
"openapi-client",
"openapi-generator",
"{{npmName}}"
],
"license": "Unlicense",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
{{#supportsES6}}
{{! Edited by OpenAI, line 22-44 }}
"module": "./dist/esm/index.mjs",
"sideEffects": false,
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"default": "./dist/esm/index.mjs"
},
"./package.json": "./package.json",
"./stream": {
"types": "./dist/stream.d.ts",
"require": "./dist/stream.js",
"default": "./dist/esm/stream.mjs"
}
},
"typesVersions": {
"*": {
"stream": [
"dist/stream.d.ts"
]
}
},
{{/supportsES6}}
"scripts": {
{{! Edited by OpenAI, line 46-48 }}
"clean": "rimraf ./dist",
"prebuild": "npm run clean && node -p \"'export const LIB_VERSION = ' + JSON.stringify(process.env.npm_package_version) + ';'\" > version.ts",
"build": "tsc {{#supportsES6}}&& tsc-multi{{/supportsES6}}",
"prepare": "npm run build"
},
"dependencies": {
"axios": "^0.27.2",
"form-data": "^4.0.0"
},
"devDependencies": {
"@types/node": "*",
"rimraf": "^3.0.2",
"tsc-multi": "^0.6.1",
"typescript": "^4.0"
}{{#npmRepository}},{{/npmRepository}}
{{#npmRepository}}
"publishConfig": {
"registry": "{{npmRepository}}"
}
{{/npmRepository}}
}
29 changes: 29 additions & 0 deletions sdk-template-overrides/typescript-axios/stream.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Readable } from 'node:stream';

async function* chunksToLines(chunksAsync: AsyncIterable<Buffer>): AsyncIterable<string> {
let previous = "";
for await (const chunk of chunksAsync) {
const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
previous += bufferChunk;
let eolIndex;
while ((eolIndex = previous.indexOf("\n")) >= 0) {
// line includes the EOL
const line = previous.slice(0, eolIndex + 1).trimEnd();
if (line === "data: [DONE]") break;
if (line.startsWith("data: ")) yield line;
previous = previous.slice(eolIndex + 1);
}
}
}

async function* linesToMessages(linesAsync: AsyncIterable<string>): AsyncIterable<string> {
for await (const line of linesAsync) {
const message = line.substring("data :".length);
yield message;
}
}

export async function* streamCompletion(stream: Readable): AsyncGenerator<string, void, undefined> {
yield* linesToMessages(chunksToLines(stream));
}
4 changes: 4 additions & 0 deletions sdk-template-overrides/typescript-axios/tsc-multi.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"targets": [{ "extname": ".mjs" }],
"projects": ["tsconfig.esm.json"]
}
27 changes: 27 additions & 0 deletions sdk-template-overrides/typescript-axios/tsconfig.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"declaration": true,
"target": "{{#supportsES6}}ES6{{/supportsES6}}{{^supportsES6}}ES5{{/supportsES6}}",
"module": "commonjs",
"esModuleInterop": true,
"noImplicitAny": true,
"outDir": "dist",
"rootDir": ".",
{{#supportsES6}}
"moduleResolution": "node",
{{/supportsES6}}
{{^supportsES6}}
"lib": [
"es6",
"dom"
],
{{/supportsES6}}
"typeRoots": [
"node_modules/@types"
]
},
"exclude": [
"dist",
"node_modules"
]
}