@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 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
- --useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
49
- --enums <value> Generate JavaScript objects from enum definitions? ['javascript', 'typescript']
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
- --lint Process output folder with linter?
54
- --format Process output folder with formatter?
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", "openapi")
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
- .option("--format", "Process output folder with formatter?")
25
- .option("--lint", "Process output folder with linter?")
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", "generics", "response"]))
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
- return method.getName();
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
+ }
@@ -1,3 +1,5 @@
1
1
  export const defaultOutputPath = "openapi";
2
2
  export const queriesOutputPath = "queries";
3
3
  export const requestsOutputPath = "requests";
4
+ export const serviceFileName = "services.gen";
5
+ export const modalsFileName = "types.gen";
@@ -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("models.ts"));
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
- // import all data objects from service file
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/models")), undefined));
39
+ ])), ts.factory.createStringLiteral(join("../requests/", modalsFileName)), undefined));
47
40
  }
48
41
  return imports;
49
42
  };
@@ -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 commonResult = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
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 = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
50
+ const mainResult = comment +
50
51
  printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
51
- const suspenseResult = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
52
+ const suspenseResult = comment +
52
53
  printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
53
- const indexResult = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
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 = ({ node, className, method, jsDoc = [], isDeprecated = false, }) => {
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, node, isDeprecated, jsDoc);
80
+ const hookWithJsDoc = addJSDocToNode(exportHook, jsDoc);
94
81
  return {
95
82
  mutationResult,
96
83
  mutationHook: hookWithJsDoc,
@@ -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 = ({ node, className, method, jsDoc = [], isDeprecated: deprecated = false, }) => {
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, node, deprecated, jsDoc);
180
- const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook, node, deprecated, jsDoc);
167
+ const hookWithJsDoc = addJSDocToNode(queryHook, jsDoc);
168
+ const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook, jsDoc);
181
169
  const returnTypeExport = createReturnTypeExport({
182
170
  className,
183
171
  methodName,
@@ -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 { defaultOutputPath, requestsOutputPath } from "./constants.mjs";
6
- import { safeParseNumber } from "./common.mjs";
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 = path.join(options.output ?? defaultOutputPath, requestsOutputPath);
9
- // loop through properties on the options object
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
- ...formattedOptions,
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 { defaultOutputPath, queriesOutputPath } from "./constants.mjs";
4
- import { exists } from "./common.mjs";
3
+ import { buildQueriesOutputPath, exists } from "./common.mjs";
5
4
  async function printGeneratedTS(result, options) {
6
- const dir = path.join(options.output ?? defaultOutputPath, queriesOutputPath);
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 ?? defaultOutputPath;
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("services.ts"));
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
- const jsDoc = method.getJsDocs().map((jsDoc) => jsDoc);
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, sourceFile, deprecated, jsDoc = []) {
3
- const deprecatedString = deprecated ? "@deprecated" : "";
4
- const jsDocString = [deprecatedString]
5
- .concat(jsDoc.map((comment) => {
6
- if (typeof comment === "string") {
7
- return comment;
8
- }
9
- if (Array.isArray(comment)) {
10
- return comment.map((c) => c.getText(sourceFile)).join("\n");
11
- }
12
- return "";
13
- }))
14
- // remove empty lines
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.1.0",
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.36.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.36.0",
44
- "commander": ">= 11 < 13",
45
- "glob": ">= 10",
46
- "ts-morph": ">= 22 < 23",
47
- "typescript": ">= 4.8.3"
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
  }