@geekmidas/cli 0.0.11 → 0.0.13
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/dist/{build-C-UDqpkV.cjs → build-CTKUZTHx.cjs} +1 -1
- package/dist/{build-DCAjSjA0.mjs → build-DgeiXkH-.mjs} +1 -1
- package/dist/build.cjs +2 -2
- package/dist/build.mjs +2 -2
- package/dist/{cli-NvCqdZtD.cjs → cli-CNAOwWIn.cjs} +14 -3
- package/dist/{cli-DPcRbYh5.mjs → cli-jxBvJiFq.mjs} +14 -3
- package/dist/cli.cjs +5 -4
- package/dist/cli.mjs +5 -4
- package/dist/index.cjs +5 -4
- package/dist/index.mjs +5 -4
- package/dist/{loadEndpoints-BL8q2rTO.mjs → loadEndpoints-DKaw6Eqm.mjs} +8 -5
- package/dist/{loadEndpoints-CYFwuPZr.cjs → loadEndpoints-Dh-dSqZX.cjs} +8 -5
- package/dist/loadEndpoints.cjs +1 -1
- package/dist/loadEndpoints.mjs +1 -1
- package/dist/{openapi-BxI6zE0N.mjs → openapi-DgLItZw-.mjs} +1 -1
- package/dist/{openapi-BX7ba0w0.cjs → openapi-DrPYAlJ_.cjs} +1 -1
- package/dist/openapi-react-query-BWpdwBix.cjs +171 -0
- package/dist/openapi-react-query-DuNQ8DwF.mjs +165 -0
- package/dist/openapi-react-query.cjs +4 -0
- package/dist/openapi-react-query.mjs +4 -0
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.mjs +2 -2
- package/package.json +2 -2
- package/src/cli.ts +32 -0
- package/src/loadEndpoints.ts +2 -1
- package/src/openapi-react-query.ts +265 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
2
|
const require_config = require('./config-D8AyiwBU.cjs');
|
|
3
|
-
const require_loadEndpoints = require('./loadEndpoints-
|
|
3
|
+
const require_loadEndpoints = require('./loadEndpoints-Dh-dSqZX.cjs');
|
|
4
4
|
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
5
5
|
const path = require_chunk.__toESM(require("path"));
|
|
6
6
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from "./config-DV1Lwdkx.mjs";
|
|
2
|
-
import { loadEndpoints } from "./loadEndpoints-
|
|
2
|
+
import { loadEndpoints } from "./loadEndpoints-DKaw6Eqm.mjs";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, relative } from "path";
|
|
5
5
|
|
package/dist/build.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require('./config-D8AyiwBU.cjs');
|
|
2
|
-
require('./loadEndpoints-
|
|
3
|
-
const require_build = require('./build-
|
|
2
|
+
require('./loadEndpoints-Dh-dSqZX.cjs');
|
|
3
|
+
const require_build = require('./build-CTKUZTHx.cjs');
|
|
4
4
|
|
|
5
5
|
exports.buildCommand = require_build.buildCommand;
|
package/dist/build.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
-
const require_build = require('./build-
|
|
3
|
-
const require_openapi = require('./openapi-
|
|
2
|
+
const require_build = require('./build-CTKUZTHx.cjs');
|
|
3
|
+
const require_openapi = require('./openapi-DrPYAlJ_.cjs');
|
|
4
|
+
const require_openapi_react_query = require('./openapi-react-query-BWpdwBix.cjs');
|
|
4
5
|
const commander = require_chunk.__toESM(require("commander"));
|
|
5
6
|
|
|
6
7
|
//#region package.json
|
|
7
8
|
var name = "@geekmidas/cli";
|
|
8
|
-
var version = "0.0.
|
|
9
|
+
var version = "0.0.13";
|
|
9
10
|
var private$1 = false;
|
|
10
11
|
var type = "module";
|
|
11
12
|
var bin = { "gkm": "./src/index.ts" };
|
|
@@ -82,6 +83,16 @@ program.command("openapi").description("Generate OpenAPI 3.0 specification from
|
|
|
82
83
|
process.exit(1);
|
|
83
84
|
}
|
|
84
85
|
});
|
|
86
|
+
program.command("generate:react-query").description("Generate React Query hooks from OpenAPI specification").option("--input <path>", "Input OpenAPI spec file path", "openapi.json").option("--output <path>", "Output file path for generated hooks", "src/api/hooks.ts").option("--name <name>", "API name prefix for generated code", "API").action(async (options) => {
|
|
87
|
+
try {
|
|
88
|
+
const globalOptions = program.opts();
|
|
89
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
90
|
+
await require_openapi_react_query.generateReactQueryCommand(options);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("React Query generation failed:", error.message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
85
96
|
program.parse();
|
|
86
97
|
|
|
87
98
|
//#endregion
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { buildCommand } from "./build-
|
|
2
|
-
import { openapiCommand } from "./openapi-
|
|
1
|
+
import { buildCommand } from "./build-DgeiXkH-.mjs";
|
|
2
|
+
import { openapiCommand } from "./openapi-DgLItZw-.mjs";
|
|
3
|
+
import { generateReactQueryCommand } from "./openapi-react-query-DuNQ8DwF.mjs";
|
|
3
4
|
import { Command } from "commander";
|
|
4
5
|
|
|
5
6
|
//#region package.json
|
|
6
7
|
var name = "@geekmidas/cli";
|
|
7
|
-
var version = "0.0.
|
|
8
|
+
var version = "0.0.13";
|
|
8
9
|
var private$1 = false;
|
|
9
10
|
var type = "module";
|
|
10
11
|
var bin = { "gkm": "./src/index.ts" };
|
|
@@ -81,6 +82,16 @@ program.command("openapi").description("Generate OpenAPI 3.0 specification from
|
|
|
81
82
|
process.exit(1);
|
|
82
83
|
}
|
|
83
84
|
});
|
|
85
|
+
program.command("generate:react-query").description("Generate React Query hooks from OpenAPI specification").option("--input <path>", "Input OpenAPI spec file path", "openapi.json").option("--output <path>", "Output file path for generated hooks", "src/api/hooks.ts").option("--name <name>", "API name prefix for generated code", "API").action(async (options) => {
|
|
86
|
+
try {
|
|
87
|
+
const globalOptions = program.opts();
|
|
88
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
89
|
+
await generateReactQueryCommand(options);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("React Query generation failed:", error.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
84
95
|
program.parse();
|
|
85
96
|
|
|
86
97
|
//#endregion
|
package/dist/cli.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
require('./config-D8AyiwBU.cjs');
|
|
3
|
-
require('./loadEndpoints-
|
|
4
|
-
require('./build-
|
|
5
|
-
require('./cli-
|
|
6
|
-
require('./openapi-
|
|
3
|
+
require('./loadEndpoints-Dh-dSqZX.cjs');
|
|
4
|
+
require('./build-CTKUZTHx.cjs');
|
|
5
|
+
require('./cli-CNAOwWIn.cjs');
|
|
6
|
+
require('./openapi-DrPYAlJ_.cjs');
|
|
7
|
+
require('./openapi-react-query-BWpdwBix.cjs');
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./config-DV1Lwdkx.mjs";
|
|
3
|
-
import "./loadEndpoints-
|
|
4
|
-
import "./build-
|
|
5
|
-
import "./cli-
|
|
6
|
-
import "./openapi-
|
|
3
|
+
import "./loadEndpoints-DKaw6Eqm.mjs";
|
|
4
|
+
import "./build-DgeiXkH-.mjs";
|
|
5
|
+
import "./cli-jxBvJiFq.mjs";
|
|
6
|
+
import "./openapi-DgLItZw-.mjs";
|
|
7
|
+
import "./openapi-react-query-DuNQ8DwF.mjs";
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
require('./config-D8AyiwBU.cjs');
|
|
3
|
-
require('./loadEndpoints-
|
|
4
|
-
require('./build-
|
|
5
|
-
require('./cli-
|
|
6
|
-
require('./openapi-
|
|
3
|
+
require('./loadEndpoints-Dh-dSqZX.cjs');
|
|
4
|
+
require('./build-CTKUZTHx.cjs');
|
|
5
|
+
require('./cli-CNAOwWIn.cjs');
|
|
6
|
+
require('./openapi-DrPYAlJ_.cjs');
|
|
7
|
+
require('./openapi-react-query-BWpdwBix.cjs');
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import "./config-DV1Lwdkx.mjs";
|
|
3
|
-
import "./loadEndpoints-
|
|
4
|
-
import "./build-
|
|
5
|
-
import "./cli-
|
|
6
|
-
import "./openapi-
|
|
3
|
+
import "./loadEndpoints-DKaw6Eqm.mjs";
|
|
4
|
+
import "./build-DgeiXkH-.mjs";
|
|
5
|
+
import "./cli-jxBvJiFq.mjs";
|
|
6
|
+
import "./openapi-DgLItZw-.mjs";
|
|
7
|
+
import "./openapi-react-query-DuNQ8DwF.mjs";
|
|
@@ -12,11 +12,14 @@ async function loadEndpoints(routes) {
|
|
|
12
12
|
for await (const f of files) try {
|
|
13
13
|
const file = f.toString();
|
|
14
14
|
const module = await import(file);
|
|
15
|
-
for (const [exportName, exportValue] of Object.entries(module)) if (Endpoint.isEndpoint(exportValue))
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
for (const [exportName, exportValue] of Object.entries(module)) if (Endpoint.isEndpoint(exportValue)) {
|
|
16
|
+
exportValue.operationId = exportName;
|
|
17
|
+
endpoints.push({
|
|
18
|
+
name: exportName,
|
|
19
|
+
endpoint: exportValue,
|
|
20
|
+
file
|
|
21
|
+
});
|
|
22
|
+
}
|
|
20
23
|
} catch (error) {
|
|
21
24
|
logger.warn(`Failed to load ${f}:`, error.message);
|
|
22
25
|
}
|
|
@@ -13,11 +13,14 @@ async function loadEndpoints(routes) {
|
|
|
13
13
|
for await (const f of files) try {
|
|
14
14
|
const file = f.toString();
|
|
15
15
|
const module$1 = await import(file);
|
|
16
|
-
for (const [exportName, exportValue] of Object.entries(module$1)) if (__geekmidas_api_server.Endpoint.isEndpoint(exportValue))
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
for (const [exportName, exportValue] of Object.entries(module$1)) if (__geekmidas_api_server.Endpoint.isEndpoint(exportValue)) {
|
|
17
|
+
exportValue.operationId = exportName;
|
|
18
|
+
endpoints.push({
|
|
19
|
+
name: exportName,
|
|
20
|
+
endpoint: exportValue,
|
|
21
|
+
file
|
|
22
|
+
});
|
|
23
|
+
}
|
|
21
24
|
} catch (error) {
|
|
22
25
|
logger.warn(`Failed to load ${f}:`, error.message);
|
|
23
26
|
}
|
package/dist/loadEndpoints.cjs
CHANGED
package/dist/loadEndpoints.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from "./config-DV1Lwdkx.mjs";
|
|
2
|
-
import { loadEndpoints } from "./loadEndpoints-
|
|
2
|
+
import { loadEndpoints } from "./loadEndpoints-DKaw6Eqm.mjs";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { Endpoint } from "@geekmidas/api/server";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
2
|
const require_config = require('./config-D8AyiwBU.cjs');
|
|
3
|
-
const require_loadEndpoints = require('./loadEndpoints-
|
|
3
|
+
const require_loadEndpoints = require('./loadEndpoints-Dh-dSqZX.cjs');
|
|
4
4
|
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
5
5
|
const __geekmidas_api_server = require_chunk.__toESM(require("@geekmidas/api/server"));
|
|
6
6
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
3
|
+
const node_path = require_chunk.__toESM(require("node:path"));
|
|
4
|
+
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
5
|
+
const node_child_process = require_chunk.__toESM(require("node:child_process"));
|
|
6
|
+
const node_util = require_chunk.__toESM(require("node:util"));
|
|
7
|
+
|
|
8
|
+
//#region src/openapi-react-query.ts
|
|
9
|
+
const execAsync = (0, node_util.promisify)(node_child_process.exec);
|
|
10
|
+
async function generateReactQueryCommand(options = {}) {
|
|
11
|
+
const logger = console;
|
|
12
|
+
try {
|
|
13
|
+
const inputPath = options.input || (0, node_path.join)(process.cwd(), "openapi.json");
|
|
14
|
+
if (!(0, node_fs.existsSync)(inputPath)) throw new Error(`OpenAPI spec not found at ${inputPath}. Run 'npx @geekmidas/cli openapi' first.`);
|
|
15
|
+
const specContent = await (0, node_fs_promises.readFile)(inputPath, "utf-8");
|
|
16
|
+
const spec = JSON.parse(specContent);
|
|
17
|
+
const outputDir = (0, node_path.dirname)(options.output || (0, node_path.join)(process.cwd(), "src", "api", "hooks.ts"));
|
|
18
|
+
const typesPath = (0, node_path.join)(outputDir, "openapi-types.d.ts");
|
|
19
|
+
logger.log("Generating TypeScript types from OpenAPI spec...");
|
|
20
|
+
try {
|
|
21
|
+
await execAsync(`npx openapi-typescript "${inputPath}" -o "${typesPath}"`, { cwd: process.cwd() });
|
|
22
|
+
logger.log(`TypeScript types generated: ${typesPath}`);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
logger.warn("Could not generate types with openapi-typescript. Install it for better type inference.");
|
|
25
|
+
logger.warn("Run: npm install -D openapi-typescript");
|
|
26
|
+
await (0, node_fs_promises.writeFile)(typesPath, `// Auto-generated placeholder types
|
|
27
|
+
export interface paths {
|
|
28
|
+
[path: string]: {
|
|
29
|
+
[method: string]: {
|
|
30
|
+
operationId?: string;
|
|
31
|
+
parameters?: any;
|
|
32
|
+
requestBody?: any;
|
|
33
|
+
responses?: any;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
const operations = extractOperations(spec);
|
|
40
|
+
const code = generateReactQueryCode(spec, operations, options.name || "API");
|
|
41
|
+
const outputPath = options.output || (0, node_path.join)(process.cwd(), "src", "api", "hooks.ts");
|
|
42
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(outputPath), { recursive: true });
|
|
43
|
+
await (0, node_fs_promises.writeFile)(outputPath, code);
|
|
44
|
+
logger.log(`React Query hooks generated: ${outputPath}`);
|
|
45
|
+
logger.log(`Generated ${operations.length} hooks`);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`React Query generation failed: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function extractOperations(spec) {
|
|
51
|
+
const operations = [];
|
|
52
|
+
Object.entries(spec.paths).forEach(([path, methods]) => {
|
|
53
|
+
Object.entries(methods).forEach(([method, operation]) => {
|
|
54
|
+
if (operation.operationId) operations.push({
|
|
55
|
+
operationId: operation.operationId,
|
|
56
|
+
path,
|
|
57
|
+
method: method.toUpperCase(),
|
|
58
|
+
endpoint: `${method.toUpperCase()} ${path}`,
|
|
59
|
+
parameters: operation.parameters,
|
|
60
|
+
requestBody: !!operation.requestBody,
|
|
61
|
+
responseType: extractResponseType(operation)
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return operations;
|
|
66
|
+
}
|
|
67
|
+
function extractResponseType(operation) {
|
|
68
|
+
const responses = operation.responses;
|
|
69
|
+
if (!responses) return "unknown";
|
|
70
|
+
const successResponse = responses["200"] || responses["201"];
|
|
71
|
+
if (!successResponse?.content?.["application/json"]?.schema) return "unknown";
|
|
72
|
+
const schema = successResponse.content["application/json"].schema;
|
|
73
|
+
return schemaToTypeString(schema);
|
|
74
|
+
}
|
|
75
|
+
function schemaToTypeString(schema) {
|
|
76
|
+
if (!schema) return "unknown";
|
|
77
|
+
switch (schema.type) {
|
|
78
|
+
case "string": return "string";
|
|
79
|
+
case "number":
|
|
80
|
+
case "integer": return "number";
|
|
81
|
+
case "boolean": return "boolean";
|
|
82
|
+
case "array": return `Array<${schemaToTypeString(schema.items)}>`;
|
|
83
|
+
case "object":
|
|
84
|
+
if (schema.properties) {
|
|
85
|
+
const props = Object.entries(schema.properties).map(([key, value]) => `${key}: ${schemaToTypeString(value)}`).join("; ");
|
|
86
|
+
return `{ ${props} }`;
|
|
87
|
+
}
|
|
88
|
+
return "Record<string, unknown>";
|
|
89
|
+
default: return "unknown";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function generateReactQueryCode(spec, operations, apiName) {
|
|
93
|
+
const imports = `import { createTypedQueryClient } from '@geekmidas/api/client';
|
|
94
|
+
import type { paths } from './openapi-types';
|
|
95
|
+
|
|
96
|
+
// Create typed query client
|
|
97
|
+
export const ${apiName.toLowerCase()} = createTypedQueryClient<paths>({
|
|
98
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || '/api',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Export individual hooks for better DX
|
|
102
|
+
`;
|
|
103
|
+
const queryHooks = operations.filter((op) => op.method === "GET").map((op) => generateQueryHook(op, apiName)).join("\n\n");
|
|
104
|
+
const mutationHooks = operations.filter((op) => op.method !== "GET").map((op) => generateMutationHook(op, apiName)).join("\n\n");
|
|
105
|
+
const typeExports = generateTypeExports(operations);
|
|
106
|
+
return `${imports}
|
|
107
|
+
// Query Hooks
|
|
108
|
+
${queryHooks}
|
|
109
|
+
|
|
110
|
+
// Mutation Hooks
|
|
111
|
+
${mutationHooks}
|
|
112
|
+
|
|
113
|
+
// Type exports for convenience
|
|
114
|
+
${typeExports}
|
|
115
|
+
|
|
116
|
+
// Re-export the api for advanced usage
|
|
117
|
+
export { ${apiName.toLowerCase()} };
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
function generateQueryHook(op, apiName) {
|
|
121
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
122
|
+
const endpoint = op.endpoint;
|
|
123
|
+
const hasParams = op.parameters?.some((p) => p.in === "path");
|
|
124
|
+
const hasQuery = op.parameters?.some((p) => p.in === "query");
|
|
125
|
+
let params = "";
|
|
126
|
+
let args = "";
|
|
127
|
+
if (hasParams || hasQuery) {
|
|
128
|
+
const paramParts = [];
|
|
129
|
+
if (hasParams) {
|
|
130
|
+
const pathParams = op.parameters?.filter((p) => p.in === "path").map((p) => p.name) || [];
|
|
131
|
+
paramParts.push(`params: { ${pathParams.map((p) => `${p}: string`).join("; ")} }`);
|
|
132
|
+
}
|
|
133
|
+
if (hasQuery) paramParts.push(`query?: Record<string, any>`);
|
|
134
|
+
params = `config: { ${paramParts.join("; ")} }, `;
|
|
135
|
+
args = ", config";
|
|
136
|
+
}
|
|
137
|
+
return `export const ${hookName} = (
|
|
138
|
+
${params}options?: Parameters<typeof ${apiName.toLowerCase()}.useQuery>[2]
|
|
139
|
+
) => {
|
|
140
|
+
return ${apiName.toLowerCase()}.useQuery('${endpoint}' as any${args}, options);
|
|
141
|
+
};`;
|
|
142
|
+
}
|
|
143
|
+
function generateMutationHook(op, apiName) {
|
|
144
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
145
|
+
const endpoint = op.endpoint;
|
|
146
|
+
return `export const ${hookName} = (
|
|
147
|
+
options?: Parameters<typeof ${apiName.toLowerCase()}.useMutation>[1]
|
|
148
|
+
) => {
|
|
149
|
+
return ${apiName.toLowerCase()}.useMutation('${endpoint}' as any, options);
|
|
150
|
+
};`;
|
|
151
|
+
}
|
|
152
|
+
function generateTypeExports(operations) {
|
|
153
|
+
const exports$1 = operations.map((op) => {
|
|
154
|
+
const typeName = capitalize(op.operationId);
|
|
155
|
+
const isQuery = op.method === "GET";
|
|
156
|
+
if (isQuery) return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['data']>>;`;
|
|
157
|
+
else return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['mutateAsync']>>;`;
|
|
158
|
+
});
|
|
159
|
+
return exports$1.join("\n");
|
|
160
|
+
}
|
|
161
|
+
function capitalize(str) {
|
|
162
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
Object.defineProperty(exports, 'generateReactQueryCommand', {
|
|
167
|
+
enumerable: true,
|
|
168
|
+
get: function () {
|
|
169
|
+
return generateReactQueryCommand;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { exec } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
//#region src/openapi-react-query.ts
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
async function generateReactQueryCommand(options = {}) {
|
|
10
|
+
const logger = console;
|
|
11
|
+
try {
|
|
12
|
+
const inputPath = options.input || join(process.cwd(), "openapi.json");
|
|
13
|
+
if (!existsSync(inputPath)) throw new Error(`OpenAPI spec not found at ${inputPath}. Run 'npx @geekmidas/cli openapi' first.`);
|
|
14
|
+
const specContent = await readFile(inputPath, "utf-8");
|
|
15
|
+
const spec = JSON.parse(specContent);
|
|
16
|
+
const outputDir = dirname(options.output || join(process.cwd(), "src", "api", "hooks.ts"));
|
|
17
|
+
const typesPath = join(outputDir, "openapi-types.d.ts");
|
|
18
|
+
logger.log("Generating TypeScript types from OpenAPI spec...");
|
|
19
|
+
try {
|
|
20
|
+
await execAsync(`npx openapi-typescript "${inputPath}" -o "${typesPath}"`, { cwd: process.cwd() });
|
|
21
|
+
logger.log(`TypeScript types generated: ${typesPath}`);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
logger.warn("Could not generate types with openapi-typescript. Install it for better type inference.");
|
|
24
|
+
logger.warn("Run: npm install -D openapi-typescript");
|
|
25
|
+
await writeFile(typesPath, `// Auto-generated placeholder types
|
|
26
|
+
export interface paths {
|
|
27
|
+
[path: string]: {
|
|
28
|
+
[method: string]: {
|
|
29
|
+
operationId?: string;
|
|
30
|
+
parameters?: any;
|
|
31
|
+
requestBody?: any;
|
|
32
|
+
responses?: any;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
const operations = extractOperations(spec);
|
|
39
|
+
const code = generateReactQueryCode(spec, operations, options.name || "API");
|
|
40
|
+
const outputPath = options.output || join(process.cwd(), "src", "api", "hooks.ts");
|
|
41
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
42
|
+
await writeFile(outputPath, code);
|
|
43
|
+
logger.log(`React Query hooks generated: ${outputPath}`);
|
|
44
|
+
logger.log(`Generated ${operations.length} hooks`);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`React Query generation failed: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function extractOperations(spec) {
|
|
50
|
+
const operations = [];
|
|
51
|
+
Object.entries(spec.paths).forEach(([path, methods]) => {
|
|
52
|
+
Object.entries(methods).forEach(([method, operation]) => {
|
|
53
|
+
if (operation.operationId) operations.push({
|
|
54
|
+
operationId: operation.operationId,
|
|
55
|
+
path,
|
|
56
|
+
method: method.toUpperCase(),
|
|
57
|
+
endpoint: `${method.toUpperCase()} ${path}`,
|
|
58
|
+
parameters: operation.parameters,
|
|
59
|
+
requestBody: !!operation.requestBody,
|
|
60
|
+
responseType: extractResponseType(operation)
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
return operations;
|
|
65
|
+
}
|
|
66
|
+
function extractResponseType(operation) {
|
|
67
|
+
const responses = operation.responses;
|
|
68
|
+
if (!responses) return "unknown";
|
|
69
|
+
const successResponse = responses["200"] || responses["201"];
|
|
70
|
+
if (!successResponse?.content?.["application/json"]?.schema) return "unknown";
|
|
71
|
+
const schema = successResponse.content["application/json"].schema;
|
|
72
|
+
return schemaToTypeString(schema);
|
|
73
|
+
}
|
|
74
|
+
function schemaToTypeString(schema) {
|
|
75
|
+
if (!schema) return "unknown";
|
|
76
|
+
switch (schema.type) {
|
|
77
|
+
case "string": return "string";
|
|
78
|
+
case "number":
|
|
79
|
+
case "integer": return "number";
|
|
80
|
+
case "boolean": return "boolean";
|
|
81
|
+
case "array": return `Array<${schemaToTypeString(schema.items)}>`;
|
|
82
|
+
case "object":
|
|
83
|
+
if (schema.properties) {
|
|
84
|
+
const props = Object.entries(schema.properties).map(([key, value]) => `${key}: ${schemaToTypeString(value)}`).join("; ");
|
|
85
|
+
return `{ ${props} }`;
|
|
86
|
+
}
|
|
87
|
+
return "Record<string, unknown>";
|
|
88
|
+
default: return "unknown";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function generateReactQueryCode(spec, operations, apiName) {
|
|
92
|
+
const imports = `import { createTypedQueryClient } from '@geekmidas/api/client';
|
|
93
|
+
import type { paths } from './openapi-types';
|
|
94
|
+
|
|
95
|
+
// Create typed query client
|
|
96
|
+
export const ${apiName.toLowerCase()} = createTypedQueryClient<paths>({
|
|
97
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || '/api',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Export individual hooks for better DX
|
|
101
|
+
`;
|
|
102
|
+
const queryHooks = operations.filter((op) => op.method === "GET").map((op) => generateQueryHook(op, apiName)).join("\n\n");
|
|
103
|
+
const mutationHooks = operations.filter((op) => op.method !== "GET").map((op) => generateMutationHook(op, apiName)).join("\n\n");
|
|
104
|
+
const typeExports = generateTypeExports(operations);
|
|
105
|
+
return `${imports}
|
|
106
|
+
// Query Hooks
|
|
107
|
+
${queryHooks}
|
|
108
|
+
|
|
109
|
+
// Mutation Hooks
|
|
110
|
+
${mutationHooks}
|
|
111
|
+
|
|
112
|
+
// Type exports for convenience
|
|
113
|
+
${typeExports}
|
|
114
|
+
|
|
115
|
+
// Re-export the api for advanced usage
|
|
116
|
+
export { ${apiName.toLowerCase()} };
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
function generateQueryHook(op, apiName) {
|
|
120
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
121
|
+
const endpoint = op.endpoint;
|
|
122
|
+
const hasParams = op.parameters?.some((p) => p.in === "path");
|
|
123
|
+
const hasQuery = op.parameters?.some((p) => p.in === "query");
|
|
124
|
+
let params = "";
|
|
125
|
+
let args = "";
|
|
126
|
+
if (hasParams || hasQuery) {
|
|
127
|
+
const paramParts = [];
|
|
128
|
+
if (hasParams) {
|
|
129
|
+
const pathParams = op.parameters?.filter((p) => p.in === "path").map((p) => p.name) || [];
|
|
130
|
+
paramParts.push(`params: { ${pathParams.map((p) => `${p}: string`).join("; ")} }`);
|
|
131
|
+
}
|
|
132
|
+
if (hasQuery) paramParts.push(`query?: Record<string, any>`);
|
|
133
|
+
params = `config: { ${paramParts.join("; ")} }, `;
|
|
134
|
+
args = ", config";
|
|
135
|
+
}
|
|
136
|
+
return `export const ${hookName} = (
|
|
137
|
+
${params}options?: Parameters<typeof ${apiName.toLowerCase()}.useQuery>[2]
|
|
138
|
+
) => {
|
|
139
|
+
return ${apiName.toLowerCase()}.useQuery('${endpoint}' as any${args}, options);
|
|
140
|
+
};`;
|
|
141
|
+
}
|
|
142
|
+
function generateMutationHook(op, apiName) {
|
|
143
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
144
|
+
const endpoint = op.endpoint;
|
|
145
|
+
return `export const ${hookName} = (
|
|
146
|
+
options?: Parameters<typeof ${apiName.toLowerCase()}.useMutation>[1]
|
|
147
|
+
) => {
|
|
148
|
+
return ${apiName.toLowerCase()}.useMutation('${endpoint}' as any, options);
|
|
149
|
+
};`;
|
|
150
|
+
}
|
|
151
|
+
function generateTypeExports(operations) {
|
|
152
|
+
const exports = operations.map((op) => {
|
|
153
|
+
const typeName = capitalize(op.operationId);
|
|
154
|
+
const isQuery = op.method === "GET";
|
|
155
|
+
if (isQuery) return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['data']>>;`;
|
|
156
|
+
else return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['mutateAsync']>>;`;
|
|
157
|
+
});
|
|
158
|
+
return exports.join("\n");
|
|
159
|
+
}
|
|
160
|
+
function capitalize(str) {
|
|
161
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
export { generateReactQueryCommand };
|
package/dist/openapi.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
require('./config-D8AyiwBU.cjs');
|
|
3
|
-
require('./loadEndpoints-
|
|
4
|
-
const require_openapi = require('./openapi-
|
|
3
|
+
require('./loadEndpoints-Dh-dSqZX.cjs');
|
|
4
|
+
const require_openapi = require('./openapi-DrPYAlJ_.cjs');
|
|
5
5
|
|
|
6
6
|
exports.openapiCommand = require_openapi.openapiCommand;
|
package/dist/openapi.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import "./config-DV1Lwdkx.mjs";
|
|
3
|
-
import "./loadEndpoints-
|
|
4
|
-
import { openapiCommand } from "./openapi-
|
|
3
|
+
import "./loadEndpoints-DKaw6Eqm.mjs";
|
|
4
|
+
import { openapiCommand } from "./openapi-DgLItZw-.mjs";
|
|
5
5
|
|
|
6
6
|
export { openapiCommand };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"lodash.set": "~4.3.2",
|
|
24
24
|
"zod": "~3.25.67",
|
|
25
25
|
"fast-glob": "~3.3.3",
|
|
26
|
-
"@geekmidas/api": "0.0.
|
|
26
|
+
"@geekmidas/api": "0.0.26"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/lodash.get": "~4.4.9",
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Command } from 'commander';
|
|
|
4
4
|
import pkg from '../package.json' assert { type: 'json' };
|
|
5
5
|
import { buildCommand } from './build.js';
|
|
6
6
|
import { openapiCommand } from './openapi.js';
|
|
7
|
+
import { generateReactQueryCommand } from './openapi-react-query.js';
|
|
7
8
|
import type { Provider } from './types.js';
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
@@ -92,4 +93,35 @@ program
|
|
|
92
93
|
}
|
|
93
94
|
});
|
|
94
95
|
|
|
96
|
+
program
|
|
97
|
+
.command('generate:react-query')
|
|
98
|
+
.description('Generate React Query hooks from OpenAPI specification')
|
|
99
|
+
.option(
|
|
100
|
+
'--input <path>',
|
|
101
|
+
'Input OpenAPI spec file path',
|
|
102
|
+
'openapi.json',
|
|
103
|
+
)
|
|
104
|
+
.option(
|
|
105
|
+
'--output <path>',
|
|
106
|
+
'Output file path for generated hooks',
|
|
107
|
+
'src/api/hooks.ts',
|
|
108
|
+
)
|
|
109
|
+
.option(
|
|
110
|
+
'--name <name>',
|
|
111
|
+
'API name prefix for generated code',
|
|
112
|
+
'API',
|
|
113
|
+
)
|
|
114
|
+
.action(async (options: { input?: string; output?: string; name?: string }) => {
|
|
115
|
+
try {
|
|
116
|
+
const globalOptions = program.opts();
|
|
117
|
+
if (globalOptions.cwd) {
|
|
118
|
+
process.chdir(globalOptions.cwd);
|
|
119
|
+
}
|
|
120
|
+
await generateReactQueryCommand(options);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('React Query generation failed:', (error as Error).message);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
95
127
|
program.parse();
|
package/src/loadEndpoints.ts
CHANGED
|
@@ -27,7 +27,8 @@ export async function loadEndpoints(routes: Routes): Promise<LoadedEndpoint[]> {
|
|
|
27
27
|
|
|
28
28
|
// Check all exports for endpoints
|
|
29
29
|
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
30
|
-
if (Endpoint.isEndpoint(exportValue
|
|
30
|
+
if (Endpoint.isEndpoint(exportValue)) {
|
|
31
|
+
exportValue.operationId = exportName;
|
|
31
32
|
endpoints.push({
|
|
32
33
|
name: exportName,
|
|
33
34
|
endpoint: exportValue as Endpoint<any, any, any, any, any, any>,
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
|
|
3
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
interface ReactQueryOptions {
|
|
12
|
+
input?: string;
|
|
13
|
+
output?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface OpenAPISpec {
|
|
18
|
+
openapi: string;
|
|
19
|
+
info?: {
|
|
20
|
+
title?: string;
|
|
21
|
+
version?: string;
|
|
22
|
+
};
|
|
23
|
+
paths: Record<string, Record<string, any>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function generateReactQueryCommand(
|
|
27
|
+
options: ReactQueryOptions = {},
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
const logger = console;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Read OpenAPI spec
|
|
33
|
+
const inputPath = options.input || join(process.cwd(), 'openapi.json');
|
|
34
|
+
|
|
35
|
+
if (!existsSync(inputPath)) {
|
|
36
|
+
throw new Error(`OpenAPI spec not found at ${inputPath}. Run 'npx @geekmidas/cli openapi' first.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const specContent = await readFile(inputPath, 'utf-8');
|
|
40
|
+
const spec: OpenAPISpec = JSON.parse(specContent);
|
|
41
|
+
|
|
42
|
+
// Generate TypeScript types from OpenAPI spec
|
|
43
|
+
const outputDir = dirname(options.output || join(process.cwd(), 'src', 'api', 'hooks.ts'));
|
|
44
|
+
const typesPath = join(outputDir, 'openapi-types.d.ts');
|
|
45
|
+
|
|
46
|
+
logger.log('Generating TypeScript types from OpenAPI spec...');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Use npx to run openapi-typescript
|
|
50
|
+
await execAsync(
|
|
51
|
+
`npx openapi-typescript "${inputPath}" -o "${typesPath}"`,
|
|
52
|
+
{ cwd: process.cwd() }
|
|
53
|
+
);
|
|
54
|
+
logger.log(`TypeScript types generated: ${typesPath}`);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.warn('Could not generate types with openapi-typescript. Install it for better type inference.');
|
|
57
|
+
logger.warn('Run: npm install -D openapi-typescript');
|
|
58
|
+
|
|
59
|
+
// Generate basic types file
|
|
60
|
+
await writeFile(typesPath, `// Auto-generated placeholder types
|
|
61
|
+
export interface paths {
|
|
62
|
+
[path: string]: {
|
|
63
|
+
[method: string]: {
|
|
64
|
+
operationId?: string;
|
|
65
|
+
parameters?: any;
|
|
66
|
+
requestBody?: any;
|
|
67
|
+
responses?: any;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Extract operation info
|
|
75
|
+
const operations = extractOperations(spec);
|
|
76
|
+
|
|
77
|
+
// Generate TypeScript code
|
|
78
|
+
const code = generateReactQueryCode(spec, operations, options.name || 'API');
|
|
79
|
+
|
|
80
|
+
// Write output
|
|
81
|
+
const outputPath = options.output || join(process.cwd(), 'src', 'api', 'hooks.ts');
|
|
82
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
83
|
+
await writeFile(outputPath, code);
|
|
84
|
+
|
|
85
|
+
logger.log(`React Query hooks generated: ${outputPath}`);
|
|
86
|
+
logger.log(`Generated ${operations.length} hooks`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new Error(`React Query generation failed: ${(error as Error).message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface OperationInfo {
|
|
93
|
+
operationId: string;
|
|
94
|
+
path: string;
|
|
95
|
+
method: string;
|
|
96
|
+
endpoint: string; // Full endpoint like 'GET /users/{id}'
|
|
97
|
+
parameters?: Array<{ name: string; in: string; required?: boolean }>;
|
|
98
|
+
requestBody?: boolean;
|
|
99
|
+
responseType?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function extractOperations(spec: OpenAPISpec): OperationInfo[] {
|
|
103
|
+
const operations: OperationInfo[] = [];
|
|
104
|
+
|
|
105
|
+
Object.entries(spec.paths).forEach(([path, methods]) => {
|
|
106
|
+
Object.entries(methods).forEach(([method, operation]) => {
|
|
107
|
+
if (operation.operationId) {
|
|
108
|
+
operations.push({
|
|
109
|
+
operationId: operation.operationId,
|
|
110
|
+
path,
|
|
111
|
+
method: method.toUpperCase(),
|
|
112
|
+
endpoint: `${method.toUpperCase()} ${path}`,
|
|
113
|
+
parameters: operation.parameters,
|
|
114
|
+
requestBody: !!operation.requestBody,
|
|
115
|
+
responseType: extractResponseType(operation),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return operations;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function extractResponseType(operation: any): string {
|
|
125
|
+
const responses = operation.responses;
|
|
126
|
+
if (!responses) return 'unknown';
|
|
127
|
+
|
|
128
|
+
const successResponse = responses['200'] || responses['201'];
|
|
129
|
+
if (!successResponse?.content?.['application/json']?.schema) {
|
|
130
|
+
return 'unknown';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Basic type inference from schema
|
|
134
|
+
const schema = successResponse.content['application/json'].schema;
|
|
135
|
+
return schemaToTypeString(schema);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function schemaToTypeString(schema: any): string {
|
|
139
|
+
if (!schema) return 'unknown';
|
|
140
|
+
|
|
141
|
+
switch (schema.type) {
|
|
142
|
+
case 'string':
|
|
143
|
+
return 'string';
|
|
144
|
+
case 'number':
|
|
145
|
+
case 'integer':
|
|
146
|
+
return 'number';
|
|
147
|
+
case 'boolean':
|
|
148
|
+
return 'boolean';
|
|
149
|
+
case 'array':
|
|
150
|
+
return `Array<${schemaToTypeString(schema.items)}>`;
|
|
151
|
+
case 'object':
|
|
152
|
+
if (schema.properties) {
|
|
153
|
+
const props = Object.entries(schema.properties)
|
|
154
|
+
.map(([key, value]: [string, any]) => `${key}: ${schemaToTypeString(value)}`)
|
|
155
|
+
.join('; ');
|
|
156
|
+
return `{ ${props} }`;
|
|
157
|
+
}
|
|
158
|
+
return 'Record<string, unknown>';
|
|
159
|
+
default:
|
|
160
|
+
return 'unknown';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function generateReactQueryCode(
|
|
165
|
+
spec: OpenAPISpec,
|
|
166
|
+
operations: OperationInfo[],
|
|
167
|
+
apiName: string,
|
|
168
|
+
): string {
|
|
169
|
+
const imports = `import { createTypedQueryClient } from '@geekmidas/api/client';
|
|
170
|
+
import type { paths } from './openapi-types';
|
|
171
|
+
|
|
172
|
+
// Create typed query client
|
|
173
|
+
export const ${apiName.toLowerCase()} = createTypedQueryClient<paths>({
|
|
174
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || '/api',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Export individual hooks for better DX
|
|
178
|
+
`;
|
|
179
|
+
|
|
180
|
+
const queryHooks = operations
|
|
181
|
+
.filter(op => op.method === 'GET')
|
|
182
|
+
.map(op => generateQueryHook(op, apiName))
|
|
183
|
+
.join('\n\n');
|
|
184
|
+
|
|
185
|
+
const mutationHooks = operations
|
|
186
|
+
.filter(op => op.method !== 'GET')
|
|
187
|
+
.map(op => generateMutationHook(op, apiName))
|
|
188
|
+
.join('\n\n');
|
|
189
|
+
|
|
190
|
+
const typeExports = generateTypeExports(operations);
|
|
191
|
+
|
|
192
|
+
return `${imports}
|
|
193
|
+
// Query Hooks
|
|
194
|
+
${queryHooks}
|
|
195
|
+
|
|
196
|
+
// Mutation Hooks
|
|
197
|
+
${mutationHooks}
|
|
198
|
+
|
|
199
|
+
// Type exports for convenience
|
|
200
|
+
${typeExports}
|
|
201
|
+
|
|
202
|
+
// Re-export the api for advanced usage
|
|
203
|
+
export { ${apiName.toLowerCase()} };
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function generateQueryHook(op: OperationInfo, apiName: string): string {
|
|
208
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
209
|
+
const endpoint = op.endpoint;
|
|
210
|
+
const hasParams = op.parameters?.some(p => p.in === 'path');
|
|
211
|
+
const hasQuery = op.parameters?.some(p => p.in === 'query');
|
|
212
|
+
|
|
213
|
+
// Generate properly typed hook
|
|
214
|
+
let params = '';
|
|
215
|
+
let args = '';
|
|
216
|
+
|
|
217
|
+
if (hasParams || hasQuery) {
|
|
218
|
+
const paramParts: string[] = [];
|
|
219
|
+
if (hasParams) {
|
|
220
|
+
const pathParams = op.parameters?.filter(p => p.in === 'path').map(p => p.name) || [];
|
|
221
|
+
paramParts.push(`params: { ${pathParams.map(p => `${p}: string`).join('; ')} }`);
|
|
222
|
+
}
|
|
223
|
+
if (hasQuery) {
|
|
224
|
+
paramParts.push(`query?: Record<string, any>`);
|
|
225
|
+
}
|
|
226
|
+
params = `config: { ${paramParts.join('; ')} }, `;
|
|
227
|
+
args = ', config';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return `export const ${hookName} = (
|
|
231
|
+
${params}options?: Parameters<typeof ${apiName.toLowerCase()}.useQuery>[2]
|
|
232
|
+
) => {
|
|
233
|
+
return ${apiName.toLowerCase()}.useQuery('${endpoint}' as any${args}, options);
|
|
234
|
+
};`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function generateMutationHook(op: OperationInfo, apiName: string): string {
|
|
238
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
239
|
+
const endpoint = op.endpoint;
|
|
240
|
+
|
|
241
|
+
return `export const ${hookName} = (
|
|
242
|
+
options?: Parameters<typeof ${apiName.toLowerCase()}.useMutation>[1]
|
|
243
|
+
) => {
|
|
244
|
+
return ${apiName.toLowerCase()}.useMutation('${endpoint}' as any, options);
|
|
245
|
+
};`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function generateTypeExports(operations: OperationInfo[]): string {
|
|
249
|
+
const exports = operations.map(op => {
|
|
250
|
+
const typeName = capitalize(op.operationId);
|
|
251
|
+
const isQuery = op.method === 'GET';
|
|
252
|
+
|
|
253
|
+
if (isQuery) {
|
|
254
|
+
return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['data']>>;`;
|
|
255
|
+
} else {
|
|
256
|
+
return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['mutateAsync']>>;`;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return exports.join('\n');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function capitalize(str: string): string {
|
|
264
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
265
|
+
}
|