@7nohe/openapi-react-query-codegen 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -18
- package/dist/cli.mjs +8 -4
- package/dist/common.mjs +72 -1
- package/dist/constants.mjs +2 -0
- package/dist/createImports.mjs +5 -12
- package/dist/createSource.mjs +5 -4
- package/dist/createUseMutation.mjs +5 -18
- package/dist/createUseQuery.mjs +5 -17
- package/dist/format.mjs +21 -0
- package/dist/generate.mjs +28 -30
- package/dist/print.mjs +3 -4
- package/dist/service.mjs +10 -25
- package/dist/util.mjs +13 -25
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -45,13 +45,16 @@ Options:
|
|
|
45
45
|
-o, --output <value> Output directory (default: "openapi")
|
|
46
46
|
-c, --client <value> HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch")
|
|
47
47
|
--request <value> Path to custom request file
|
|
48
|
-
--
|
|
49
|
-
--
|
|
50
|
-
--base <value> Manually set base in OpenAPI config instead of inferring from server value
|
|
51
|
-
--serviceResponse <value> Define shape of returned value from service calls ['body', 'generics', 'response']
|
|
48
|
+
--format <value> Process output folder with formatter? ['biome', 'prettier']
|
|
49
|
+
--lint <value> Process output folder with linter? ['eslint', 'biome']
|
|
52
50
|
--operationId Use operation ID to generate operation names?
|
|
53
|
-
--
|
|
54
|
-
--
|
|
51
|
+
--serviceResponse <value> Define shape of returned value from service calls ['body', 'generics', 'response']
|
|
52
|
+
--base <value> Manually set base in OpenAPI config instead of inferring from server value
|
|
53
|
+
--enums <value> Generate JavaScript objects from enum definitions? ['javascript', 'typescript']
|
|
54
|
+
--useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
|
|
55
|
+
--debug Enable debug mode
|
|
56
|
+
--noSchemas Disable generating schemas for request and response objects
|
|
57
|
+
--schemaTypes <value> Define the type of schema generation ['form', 'json'] (default: "json")
|
|
55
58
|
-h, --help display help for command
|
|
56
59
|
```
|
|
57
60
|
|
|
@@ -86,11 +89,7 @@ function App() {
|
|
|
86
89
|
return (
|
|
87
90
|
<div className="App">
|
|
88
91
|
<h1>Pet List</h1>
|
|
89
|
-
<ul>
|
|
90
|
-
{data?.map((pet) => (
|
|
91
|
-
<li key={pet.id}>{pet.name}</li>
|
|
92
|
-
))}
|
|
93
|
-
</ul>
|
|
92
|
+
<ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
|
|
94
93
|
</div>
|
|
95
94
|
);
|
|
96
95
|
}
|
|
@@ -129,13 +128,7 @@ import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
|
|
|
129
128
|
function ChildComponent() {
|
|
130
129
|
const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
|
|
131
130
|
|
|
132
|
-
return (
|
|
133
|
-
<ul>
|
|
134
|
-
{data?.map((pet, index) => (
|
|
135
|
-
<li key={pet.id}>{pet.name}</li>
|
|
136
|
-
))}
|
|
137
|
-
</ul>
|
|
138
|
-
);
|
|
131
|
+
return <ul>{data?.map((pet, index) => <li key={pet.id}>{pet.name}</li>)}</ul>;
|
|
139
132
|
}
|
|
140
133
|
|
|
141
134
|
function ParentComponent() {
|
package/dist/cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { Command, Option } from "commander";
|
|
|
4
4
|
import { readFile } from "fs/promises";
|
|
5
5
|
import { dirname, join } from "path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { defaultOutputPath } from "./constants.mjs";
|
|
7
8
|
const program = new Command();
|
|
8
9
|
async function setupProgram() {
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -16,18 +17,21 @@ async function setupProgram() {
|
|
|
16
17
|
.version(version)
|
|
17
18
|
.description("Generate React Query code based on OpenAPI")
|
|
18
19
|
.requiredOption("-i, --input <value>", "OpenAPI specification, can be a path, url or string content (required)")
|
|
19
|
-
.option("-o, --output <value>", "Output directory",
|
|
20
|
+
.option("-o, --output <value>", "Output directory", defaultOutputPath)
|
|
20
21
|
.addOption(new Option("-c, --client <value>", "HTTP client to generate")
|
|
21
22
|
.choices(["angular", "axios", "fetch", "node", "xhr"])
|
|
22
23
|
.default("fetch"))
|
|
23
24
|
.option("--request <value>", "Path to custom request file")
|
|
24
|
-
.
|
|
25
|
-
.
|
|
25
|
+
.addOption(new Option("--format <value>", "Process output folder with formatter?").choices(["biome", "prettier"]))
|
|
26
|
+
.addOption(new Option("--lint <value>", "Process output folder with linter?").choices(["biome", "eslint"]))
|
|
26
27
|
.option("--operationId", "Use operation ID to generate operation names?")
|
|
27
|
-
.addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls").choices(["body", "
|
|
28
|
+
.addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls").choices(["body", "response"]))
|
|
28
29
|
.option("--base <value>", "Manually set base in OpenAPI config instead of inferring from server value")
|
|
29
30
|
.addOption(new Option("--enums <value>", "Generate JavaScript objects from enum definitions?").choices(["javascript", "typescript"]))
|
|
30
31
|
.option("--useDateType", "Use Date type instead of string for date types for models, this will not convert the data to a Date object")
|
|
32
|
+
.option("--debug", "Run in debug mode?")
|
|
33
|
+
.option("--noSchemas", "Disable generating JSON schemas")
|
|
34
|
+
.addOption(new Option("--schemaType <value>", "Type of JSON schema [Default: 'json']").choices(["form", "json"]))
|
|
31
35
|
.parse();
|
|
32
36
|
const options = program.opts();
|
|
33
37
|
await generate(options, version);
|
package/dist/common.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { stat } from "fs/promises";
|
|
2
2
|
import ts from "typescript";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { queriesOutputPath, requestsOutputPath } from "./constants.mjs";
|
|
3
5
|
export const TData = ts.factory.createIdentifier("TData");
|
|
4
6
|
export const TError = ts.factory.createIdentifier("TError");
|
|
5
7
|
export const TContext = ts.factory.createIdentifier("TContext");
|
|
@@ -14,7 +16,11 @@ export const lowercaseFirstLetter = (str) => {
|
|
|
14
16
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
15
17
|
};
|
|
16
18
|
export const getNameFromMethod = (method) => {
|
|
17
|
-
|
|
19
|
+
const methodName = method.getName();
|
|
20
|
+
if (!methodName) {
|
|
21
|
+
throw new Error("Method name not found");
|
|
22
|
+
}
|
|
23
|
+
return methodName;
|
|
18
24
|
};
|
|
19
25
|
export async function exists(f) {
|
|
20
26
|
try {
|
|
@@ -62,3 +68,68 @@ export function extractPropertiesFromObjectParam(param) {
|
|
|
62
68
|
}));
|
|
63
69
|
return paramNodes;
|
|
64
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Replace the import("...") surrounding the type if there is one.
|
|
73
|
+
* This can happen when the type is imported from another file, but
|
|
74
|
+
* we are already importing all the types from that file.
|
|
75
|
+
*
|
|
76
|
+
* https://regex101.com/r/3DyHaQ/1
|
|
77
|
+
*
|
|
78
|
+
* TODO: Replace with a more robust solution.
|
|
79
|
+
*/
|
|
80
|
+
export function getShortType(type) {
|
|
81
|
+
return type.replaceAll(/import\(".*"\)\./g, "");
|
|
82
|
+
}
|
|
83
|
+
export function getClassesFromService(node) {
|
|
84
|
+
const klasses = node.getClasses();
|
|
85
|
+
if (!klasses.length) {
|
|
86
|
+
throw new Error("No classes found");
|
|
87
|
+
}
|
|
88
|
+
return klasses.map((klass) => {
|
|
89
|
+
const className = klass.getName();
|
|
90
|
+
if (!className) {
|
|
91
|
+
throw new Error("Class name not found");
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
className,
|
|
95
|
+
klass,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export function getClassNameFromClassNode(klass) {
|
|
100
|
+
const className = klass.getName();
|
|
101
|
+
if (!className) {
|
|
102
|
+
throw new Error("Class name not found");
|
|
103
|
+
}
|
|
104
|
+
return className;
|
|
105
|
+
}
|
|
106
|
+
export function formatOptions(options) {
|
|
107
|
+
// loop through properties on the options object
|
|
108
|
+
// if the property is a string of number then convert it to a number
|
|
109
|
+
// if the property is a string of boolean then convert it to a boolean
|
|
110
|
+
const formattedOptions = Object.entries(options).reduce((acc, [key, value]) => {
|
|
111
|
+
const typedKey = key;
|
|
112
|
+
const typedValue = value;
|
|
113
|
+
const parsedNumber = safeParseNumber(typedValue);
|
|
114
|
+
if (value === "true" || value === true) {
|
|
115
|
+
acc[typedKey] = true;
|
|
116
|
+
}
|
|
117
|
+
else if (value === "false" || value === false) {
|
|
118
|
+
acc[typedKey] = false;
|
|
119
|
+
}
|
|
120
|
+
else if (!isNaN(parsedNumber)) {
|
|
121
|
+
acc[typedKey] = parsedNumber;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
acc[typedKey] = typedValue;
|
|
125
|
+
}
|
|
126
|
+
return acc;
|
|
127
|
+
}, options);
|
|
128
|
+
return formattedOptions;
|
|
129
|
+
}
|
|
130
|
+
export function buildRequestsOutputPath(outputPath) {
|
|
131
|
+
return path.join(outputPath, requestsOutputPath);
|
|
132
|
+
}
|
|
133
|
+
export function buildQueriesOutputPath(outputPath) {
|
|
134
|
+
return path.join(outputPath, queriesOutputPath);
|
|
135
|
+
}
|
package/dist/constants.mjs
CHANGED
package/dist/createImports.mjs
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { posix } from "path";
|
|
3
|
+
import { modalsFileName, serviceFileName } from "./constants.mjs";
|
|
3
4
|
const { join } = posix;
|
|
4
5
|
export const createImports = ({ serviceEndName, project, }) => {
|
|
5
6
|
const modelsFile = project
|
|
6
7
|
.getSourceFiles()
|
|
7
|
-
.find((sourceFile) => sourceFile.getFilePath().includes(
|
|
8
|
-
const serviceFile = project
|
|
9
|
-
.getSourceFiles()
|
|
10
|
-
.find((sourceFile) => sourceFile.getFilePath().includes("services.ts"));
|
|
8
|
+
.find((sourceFile) => sourceFile.getFilePath().includes(modalsFileName));
|
|
9
|
+
const serviceFile = project.getSourceFileOrThrow(`${serviceFileName}.ts`);
|
|
11
10
|
if (!modelsFile) {
|
|
12
11
|
console.warn(`
|
|
13
12
|
⚠️ WARNING: No models file found.
|
|
14
13
|
This may be an error if \`.components.schemas\` or \`.components.parameters\` is defined in your OpenAPI input.`);
|
|
15
14
|
}
|
|
16
|
-
if (!serviceFile) {
|
|
17
|
-
throw new Error("No service file found");
|
|
18
|
-
}
|
|
19
15
|
const modelNames = modelsFile
|
|
20
16
|
? Array.from(modelsFile.getExportedDeclarations().keys())
|
|
21
17
|
: [];
|
|
22
18
|
const serviceExports = Array.from(serviceFile.getExportedDeclarations().keys());
|
|
23
19
|
const serviceNames = serviceExports.filter((name) => name.endsWith(serviceEndName));
|
|
24
|
-
const serviceNamesData = serviceExports.filter((name) => name.endsWith("Data"));
|
|
25
20
|
const imports = [
|
|
26
21
|
ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
27
22
|
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("useQuery")),
|
|
@@ -35,15 +30,13 @@ export const createImports = ({ serviceEndName, project, }) => {
|
|
|
35
30
|
ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
36
31
|
// import all class names from service file
|
|
37
32
|
...serviceNames.map((serviceName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(serviceName))),
|
|
38
|
-
|
|
39
|
-
...serviceNamesData.map((dataName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(dataName))),
|
|
40
|
-
])), ts.factory.createStringLiteral(join("../requests")), undefined),
|
|
33
|
+
])), ts.factory.createStringLiteral(join("../requests", serviceFileName)), undefined),
|
|
41
34
|
];
|
|
42
35
|
if (modelsFile) {
|
|
43
36
|
// import all the models by name
|
|
44
37
|
imports.push(ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
45
38
|
...modelNames.map((modelName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(modelName))),
|
|
46
|
-
])), ts.factory.createStringLiteral(join("../requests/
|
|
39
|
+
])), ts.factory.createStringLiteral(join("../requests/", modalsFileName)), undefined));
|
|
47
40
|
}
|
|
48
41
|
return imports;
|
|
49
42
|
};
|
package/dist/createSource.mjs
CHANGED
|
@@ -44,13 +44,14 @@ export const createSource = async ({ outputPath, version, serviceEndName, }) =>
|
|
|
44
44
|
removeComments: false,
|
|
45
45
|
});
|
|
46
46
|
const { commonSource, mainSource, suspenseSource, indexSource } = await createSourceFile(outputPath, serviceEndName);
|
|
47
|
-
const
|
|
47
|
+
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
|
|
48
|
+
const commonResult = comment +
|
|
48
49
|
printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
|
|
49
|
-
const mainResult =
|
|
50
|
+
const mainResult = comment +
|
|
50
51
|
printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
|
|
51
|
-
const suspenseResult =
|
|
52
|
+
const suspenseResult = comment +
|
|
52
53
|
printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
|
|
53
|
-
const indexResult =
|
|
54
|
+
const indexResult = comment +
|
|
54
55
|
printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
|
|
55
56
|
return [
|
|
56
57
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { BuildCommonTypeName, TContext, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, } from "./common.mjs";
|
|
2
|
+
import { BuildCommonTypeName, TContext, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, } from "./common.mjs";
|
|
3
3
|
import { addJSDocToNode } from "./util.mjs";
|
|
4
4
|
/**
|
|
5
5
|
* Awaited<ReturnType<typeof myClass.myMethod>>
|
|
@@ -11,7 +11,7 @@ function generateAwaitedReturnType({ className, methodName, }) {
|
|
|
11
11
|
]),
|
|
12
12
|
]);
|
|
13
13
|
}
|
|
14
|
-
export const createUseMutation = ({
|
|
14
|
+
export const createUseMutation = ({ className, method, jsDoc, }) => {
|
|
15
15
|
const methodName = getNameFromMethod(method);
|
|
16
16
|
const awaitedResponseDataType = generateAwaitedReturnType({
|
|
17
17
|
className,
|
|
@@ -26,22 +26,9 @@ export const createUseMutation = ({ node, className, method, jsDoc = [], isDepre
|
|
|
26
26
|
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
27
27
|
return paramNodes.map((refParam) => ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
28
28
|
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
29
|
-
: undefined,
|
|
30
|
-
// refParam.questionToken ?? refParam.initializer
|
|
31
|
-
// ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
32
|
-
// : refParam.questionToken,
|
|
33
|
-
ts.factory.createTypeReferenceNode(refParam.type.getText(param))));
|
|
29
|
+
: undefined, ts.factory.createTypeReferenceNode(getShortType(refParam.type.getText(param)))));
|
|
34
30
|
})
|
|
35
|
-
.flat()
|
|
36
|
-
// return ts.factory.createPropertySignature(
|
|
37
|
-
// undefined,
|
|
38
|
-
// ts.factory.createIdentifier(param.getName()),
|
|
39
|
-
// param.compilerNode.questionToken ?? param.compilerNode.initializer
|
|
40
|
-
// ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
41
|
-
// : param.compilerNode.questionToken,
|
|
42
|
-
// param.compilerNode.type
|
|
43
|
-
// );
|
|
44
|
-
)
|
|
31
|
+
.flat())
|
|
45
32
|
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
46
33
|
const exportHook = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
47
34
|
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`use${className}${capitalizeFirstLetter(methodName)}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
|
|
@@ -90,7 +77,7 @@ export const createUseMutation = ({ node, className, method, jsDoc = [], isDepre
|
|
|
90
77
|
]),
|
|
91
78
|
]))),
|
|
92
79
|
], ts.NodeFlags.Const));
|
|
93
|
-
const hookWithJsDoc = addJSDocToNode(exportHook,
|
|
80
|
+
const hookWithJsDoc = addJSDocToNode(exportHook, jsDoc);
|
|
94
81
|
return {
|
|
95
82
|
mutationResult,
|
|
96
83
|
mutationHook: hookWithJsDoc,
|
package/dist/createUseQuery.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { BuildCommonTypeName, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, queryKeyConstraint, queryKeyGenericType, TData, TError, } from "./common.mjs";
|
|
2
|
+
import { BuildCommonTypeName, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, queryKeyConstraint, queryKeyGenericType, TData, TError, } from "./common.mjs";
|
|
3
3
|
import { addJSDocToNode } from "./util.mjs";
|
|
4
4
|
export const createApiResponseType = ({ className, methodName, }) => {
|
|
5
5
|
/** Awaited<ReturnType<typeof myClass.myMethod>> */
|
|
@@ -25,14 +25,6 @@ export const createApiResponseType = ({ className, methodName, }) => {
|
|
|
25
25
|
responseDataType,
|
|
26
26
|
};
|
|
27
27
|
};
|
|
28
|
-
/**
|
|
29
|
-
* Replace the import("...") surrounding the type if there is one.
|
|
30
|
-
* This can happen when the type is imported from another file, but
|
|
31
|
-
* we are already importing all the types from that file.
|
|
32
|
-
*/
|
|
33
|
-
function getShortType(type) {
|
|
34
|
-
return type.replaceAll(/import\("[a-zA-Z\/\.-]*"\)\./g, "");
|
|
35
|
-
}
|
|
36
28
|
export function getRequestParamFromMethod(method) {
|
|
37
29
|
if (!method.getParameters().length) {
|
|
38
30
|
return null;
|
|
@@ -52,11 +44,7 @@ export function getRequestParamFromMethod(method) {
|
|
|
52
44
|
return ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createObjectBindingPattern(params.map((refParam) => ts.factory.createBindingElement(undefined, undefined, ts.factory.createIdentifier(refParam.name), undefined))), undefined, ts.factory.createTypeLiteralNode(params.map((refParam) => {
|
|
53
45
|
return ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
54
46
|
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
55
|
-
: undefined,
|
|
56
|
-
// param.hasQuestionToken() ?? param.getInitializer()?.compilerNode
|
|
57
|
-
// ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
58
|
-
// : param.getQuestionTokenNode()?.compilerNode,
|
|
59
|
-
ts.factory.createTypeReferenceNode(refParam.typeName));
|
|
47
|
+
: undefined, ts.factory.createTypeReferenceNode(refParam.typeName));
|
|
60
48
|
})),
|
|
61
49
|
// if all params are optional, we create an empty object literal
|
|
62
50
|
// so the hook can be called without any parameters
|
|
@@ -151,7 +139,7 @@ function createQueryHook({ queryString, suffix, responseDataType, requestParams,
|
|
|
151
139
|
], ts.NodeFlags.Const));
|
|
152
140
|
return hookExport;
|
|
153
141
|
}
|
|
154
|
-
export const createUseQuery = ({
|
|
142
|
+
export const createUseQuery = ({ className, method, jsDoc, }) => {
|
|
155
143
|
const methodName = getNameFromMethod(method);
|
|
156
144
|
const queryKey = createQueryKeyFromMethod({ method, className });
|
|
157
145
|
const { apiResponse: defaultApiResponse, responseDataType } = createApiResponseType({
|
|
@@ -176,8 +164,8 @@ export const createUseQuery = ({ node, className, method, jsDoc = [], isDeprecat
|
|
|
176
164
|
method,
|
|
177
165
|
className,
|
|
178
166
|
});
|
|
179
|
-
const hookWithJsDoc = addJSDocToNode(queryHook,
|
|
180
|
-
const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook,
|
|
167
|
+
const hookWithJsDoc = addJSDocToNode(queryHook, jsDoc);
|
|
168
|
+
const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook, jsDoc);
|
|
181
169
|
const returnTypeExport = createReturnTypeExport({
|
|
182
170
|
className,
|
|
183
171
|
methodName,
|
package/dist/format.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IndentationText, NewLineKind, Project, QuoteKind } from "ts-morph";
|
|
2
|
+
export const formatOutput = async (outputPath) => {
|
|
3
|
+
const project = new Project({
|
|
4
|
+
skipAddingFilesFromTsConfig: true,
|
|
5
|
+
manipulationSettings: {
|
|
6
|
+
indentationText: IndentationText.TwoSpaces,
|
|
7
|
+
newLineKind: NewLineKind.LineFeed,
|
|
8
|
+
quoteKind: QuoteKind.Double,
|
|
9
|
+
usePrefixAndSuffixTextForRename: false,
|
|
10
|
+
useTrailingCommas: true,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const sourceFiles = project.addSourceFilesAtPaths(`${outputPath}/**/*`);
|
|
14
|
+
const tasks = sourceFiles.map((sourceFile) => {
|
|
15
|
+
sourceFile.formatText();
|
|
16
|
+
sourceFile.fixMissingImports();
|
|
17
|
+
sourceFile.organizeImports();
|
|
18
|
+
return sourceFile.save();
|
|
19
|
+
});
|
|
20
|
+
await Promise.all(tasks);
|
|
21
|
+
};
|
package/dist/generate.mjs
CHANGED
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
import { createClient } from "@hey-api/openapi-ts";
|
|
2
2
|
import { print } from "./print.mjs";
|
|
3
|
-
import path from "path";
|
|
4
3
|
import { createSource } from "./createSource.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { buildQueriesOutputPath, buildRequestsOutputPath, formatOptions, } from "./common.mjs";
|
|
5
|
+
import { formatOutput } from "./format.mjs";
|
|
7
6
|
export async function generate(options, version) {
|
|
8
|
-
const openApiOutputPath =
|
|
9
|
-
|
|
10
|
-
// if the property is a string of number then convert it to a number
|
|
11
|
-
// if the property is a string of boolean then convert it to a boolean
|
|
12
|
-
const formattedOptions = Object.entries(options).reduce((acc, [key, value]) => {
|
|
13
|
-
const typedKey = key;
|
|
14
|
-
const typedValue = value;
|
|
15
|
-
const parsedNumber = safeParseNumber(typedValue);
|
|
16
|
-
if (!isNaN(parsedNumber)) {
|
|
17
|
-
acc[typedKey] = parsedNumber;
|
|
18
|
-
}
|
|
19
|
-
else if (value === "true") {
|
|
20
|
-
acc[typedKey] = true;
|
|
21
|
-
}
|
|
22
|
-
else if (value === "false") {
|
|
23
|
-
acc[typedKey] = false;
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
acc[typedKey] = typedValue;
|
|
27
|
-
}
|
|
28
|
-
return acc;
|
|
29
|
-
}, options);
|
|
7
|
+
const openApiOutputPath = buildRequestsOutputPath(options.output);
|
|
8
|
+
const formattedOptions = formatOptions(options);
|
|
30
9
|
const config = {
|
|
31
|
-
|
|
10
|
+
base: formattedOptions.base,
|
|
11
|
+
client: formattedOptions.client,
|
|
12
|
+
debug: formattedOptions.debug,
|
|
13
|
+
dryRun: false,
|
|
14
|
+
enums: formattedOptions.enums,
|
|
15
|
+
exportCore: true,
|
|
16
|
+
format: formattedOptions.format,
|
|
17
|
+
input: formattedOptions.input,
|
|
18
|
+
lint: formattedOptions.lint,
|
|
32
19
|
output: openApiOutputPath,
|
|
20
|
+
request: formattedOptions.request,
|
|
21
|
+
schemas: {
|
|
22
|
+
export: !formattedOptions.noSchemas,
|
|
23
|
+
type: formattedOptions.schemaType,
|
|
24
|
+
},
|
|
25
|
+
services: {
|
|
26
|
+
export: true,
|
|
27
|
+
response: formattedOptions.serviceResponse,
|
|
28
|
+
},
|
|
29
|
+
types: {
|
|
30
|
+
dates: formattedOptions.useDateType,
|
|
31
|
+
export: true,
|
|
32
|
+
},
|
|
33
33
|
useOptions: true,
|
|
34
|
-
exportCore: true,
|
|
35
|
-
exportModels: true,
|
|
36
|
-
exportServices: true,
|
|
37
|
-
write: true,
|
|
38
34
|
};
|
|
39
35
|
await createClient(config);
|
|
40
36
|
const source = await createSource({
|
|
@@ -43,4 +39,6 @@ export async function generate(options, version) {
|
|
|
43
39
|
serviceEndName: "Service", // we are hard coding this because changing the service end name was depreciated in @hey-api/openapi-ts
|
|
44
40
|
});
|
|
45
41
|
await print(source, formattedOptions);
|
|
42
|
+
const queriesOutputPath = buildQueriesOutputPath(options.output);
|
|
43
|
+
await formatOutput(queriesOutputPath);
|
|
46
44
|
}
|
package/dist/print.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import {
|
|
4
|
-
import { exists } from "./common.mjs";
|
|
3
|
+
import { buildQueriesOutputPath, exists } from "./common.mjs";
|
|
5
4
|
async function printGeneratedTS(result, options) {
|
|
6
|
-
const dir =
|
|
5
|
+
const dir = buildQueriesOutputPath(options.output);
|
|
7
6
|
const dirExists = await exists(dir);
|
|
8
7
|
if (!dirExists) {
|
|
9
8
|
await mkdir(dir, { recursive: true });
|
|
@@ -11,7 +10,7 @@ async function printGeneratedTS(result, options) {
|
|
|
11
10
|
await writeFile(path.join(dir, result.name), result.content);
|
|
12
11
|
}
|
|
13
12
|
export async function print(results, options) {
|
|
14
|
-
const outputPath = options.output
|
|
13
|
+
const outputPath = options.output;
|
|
15
14
|
const dirExists = await exists(outputPath);
|
|
16
15
|
if (!dirExists) {
|
|
17
16
|
await mkdir(outputPath);
|
package/dist/service.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { getClassNameFromClassNode, getClassesFromService, } from "./common.mjs";
|
|
3
|
+
import { serviceFileName } from "./constants.mjs";
|
|
2
4
|
export async function getServices(project) {
|
|
3
5
|
const node = project
|
|
4
6
|
.getSourceFiles()
|
|
5
|
-
.find((sourceFile) => sourceFile.getFilePath().includes(
|
|
7
|
+
.find((sourceFile) => sourceFile.getFilePath().includes(serviceFileName));
|
|
6
8
|
if (!node) {
|
|
7
9
|
throw new Error("No service node found");
|
|
8
10
|
}
|
|
@@ -16,29 +18,6 @@ export async function getServices(project) {
|
|
|
16
18
|
node,
|
|
17
19
|
};
|
|
18
20
|
}
|
|
19
|
-
function getClassesFromService(node) {
|
|
20
|
-
const klasses = node.getClasses();
|
|
21
|
-
if (!klasses.length) {
|
|
22
|
-
throw new Error("No classes found");
|
|
23
|
-
}
|
|
24
|
-
return klasses.map((klass) => {
|
|
25
|
-
const className = klass.getName();
|
|
26
|
-
if (!className) {
|
|
27
|
-
throw new Error("Class name not found");
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
className,
|
|
31
|
-
klass,
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function getClassNameFromClassNode(klass) {
|
|
36
|
-
const className = klass.getName();
|
|
37
|
-
if (!className) {
|
|
38
|
-
throw new Error("Class name not found");
|
|
39
|
-
}
|
|
40
|
-
return className;
|
|
41
|
-
}
|
|
42
21
|
function getMethodsFromService(node, klass) {
|
|
43
22
|
const methods = klass.getMethods();
|
|
44
23
|
if (!methods.length) {
|
|
@@ -78,7 +57,13 @@ function getMethodsFromService(node, klass) {
|
|
|
78
57
|
return [tsNode];
|
|
79
58
|
};
|
|
80
59
|
const children = getAllChildren(method.compilerNode);
|
|
81
|
-
|
|
60
|
+
// get all JSDoc comments
|
|
61
|
+
// this should be an array of 1 or 0
|
|
62
|
+
const jsDocs = children
|
|
63
|
+
.filter((c) => c.kind === ts.SyntaxKind.JSDoc)
|
|
64
|
+
.map((c) => c.getText(node.compilerNode));
|
|
65
|
+
// get the first JSDoc comment
|
|
66
|
+
const jsDoc = jsDocs?.[0];
|
|
82
67
|
const isDeprecated = children.some((c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag);
|
|
83
68
|
const className = getClassNameFromClassNode(klass);
|
|
84
69
|
return {
|
package/dist/util.mjs
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
export function addJSDocToNode(node,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.filter(Boolean)
|
|
16
|
-
// trim
|
|
17
|
-
.map((comment) => comment.trim())
|
|
18
|
-
// add * to each line
|
|
19
|
-
.map((comment) => `* ${comment}`)
|
|
20
|
-
// join lines
|
|
21
|
-
.join("\n")
|
|
22
|
-
// replace new lines with \n *
|
|
23
|
-
.replace(/\n/g, "\n * ");
|
|
24
|
-
const nodeWithJSDoc = jsDocString
|
|
25
|
-
? ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `*\n ${jsDocString}\n `, true)
|
|
26
|
-
: node;
|
|
2
|
+
export function addJSDocToNode(node, jsDoc) {
|
|
3
|
+
if (!jsDoc) {
|
|
4
|
+
return node;
|
|
5
|
+
}
|
|
6
|
+
// replace the first /** with *
|
|
7
|
+
// we do this because ts.addSyntheticLeadingComment will add /* to the beginning but we want /**
|
|
8
|
+
const removedFirstLine = jsDoc.trim().replace(/^\/\*\*/, "*");
|
|
9
|
+
// remove the last */ because ts.addSyntheticLeadingComment will add it
|
|
10
|
+
const removedSecondLine = removedFirstLine.replace(/\*\/$/, "");
|
|
11
|
+
const split = removedSecondLine.split("\n");
|
|
12
|
+
const trimmed = split.map((line) => line.trim());
|
|
13
|
+
const joined = trimmed.join("\n");
|
|
14
|
+
const nodeWithJSDoc = ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, joined, true);
|
|
27
15
|
return nodeWithJSDoc;
|
|
28
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7nohe/openapi-react-query-codegen",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "OpenAPI React Query Codegen",
|
|
5
5
|
"bin": {
|
|
6
6
|
"openapi-rq": "dist/cli.mjs"
|
|
@@ -31,20 +31,22 @@
|
|
|
31
31
|
"author": "Daiki Urata (@7nohe)",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@hey-api/openapi-ts": "0.
|
|
34
|
+
"@hey-api/openapi-ts": "0.42.1",
|
|
35
35
|
"@types/node": "^20.10.6",
|
|
36
|
+
"@vitest/coverage-v8": "^1.5.0",
|
|
36
37
|
"commander": "^12.0.0",
|
|
37
38
|
"glob": "^10.3.10",
|
|
38
39
|
"rimraf": "^5.0.5",
|
|
39
40
|
"ts-morph": "^22.0.0",
|
|
40
|
-
"typescript": "^5.3.3"
|
|
41
|
+
"typescript": "^5.3.3",
|
|
42
|
+
"vitest": "^1.5.0"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
43
|
-
"@hey-api/openapi-ts": "0.
|
|
44
|
-
"commander": "
|
|
45
|
-
"glob": "
|
|
46
|
-
"ts-morph": "
|
|
47
|
-
"typescript": "
|
|
45
|
+
"@hey-api/openapi-ts": "0.42.1",
|
|
46
|
+
"commander": "12.x",
|
|
47
|
+
"glob": "10.x",
|
|
48
|
+
"ts-morph": "22.x",
|
|
49
|
+
"typescript": "5.x"
|
|
48
50
|
},
|
|
49
51
|
"engines": {
|
|
50
52
|
"node": ">=14"
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
"scripts": {
|
|
53
55
|
"build": "rimraf dist && tsc -p tsconfig.json",
|
|
54
56
|
"preview": "npm run build && npm -C examples/react-app run generate:api",
|
|
55
|
-
"release": "npx git-ensure -a && npx bumpp --commit --tag --push"
|
|
57
|
+
"release": "npx git-ensure -a && npx bumpp --commit --tag --push",
|
|
58
|
+
"test": "vitest --coverage.enabled true"
|
|
56
59
|
}
|
|
57
60
|
}
|