@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.
@@ -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-CYFwuPZr.cjs');
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-BL8q2rTO.mjs";
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-CYFwuPZr.cjs');
3
- const require_build = require('./build-C-UDqpkV.cjs');
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,5 +1,5 @@
1
1
  import "./config-DV1Lwdkx.mjs";
2
- import "./loadEndpoints-BL8q2rTO.mjs";
3
- import { buildCommand } from "./build-DCAjSjA0.mjs";
2
+ import "./loadEndpoints-DKaw6Eqm.mjs";
3
+ import { buildCommand } from "./build-DgeiXkH-.mjs";
4
4
 
5
5
  export { buildCommand };
@@ -1,11 +1,12 @@
1
1
  const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const require_build = require('./build-C-UDqpkV.cjs');
3
- const require_openapi = require('./openapi-BX7ba0w0.cjs');
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.11";
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-DCAjSjA0.mjs";
2
- import { openapiCommand } from "./openapi-BxI6zE0N.mjs";
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.11";
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-CYFwuPZr.cjs');
4
- require('./build-C-UDqpkV.cjs');
5
- require('./cli-NvCqdZtD.cjs');
6
- require('./openapi-BX7ba0w0.cjs');
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-BL8q2rTO.mjs";
4
- import "./build-DCAjSjA0.mjs";
5
- import "./cli-DPcRbYh5.mjs";
6
- import "./openapi-BxI6zE0N.mjs";
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-CYFwuPZr.cjs');
4
- require('./build-C-UDqpkV.cjs');
5
- require('./cli-NvCqdZtD.cjs');
6
- require('./openapi-BX7ba0w0.cjs');
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-BL8q2rTO.mjs";
4
- import "./build-DCAjSjA0.mjs";
5
- import "./cli-DPcRbYh5.mjs";
6
- import "./openapi-BxI6zE0N.mjs";
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)) endpoints.push({
16
- name: exportName,
17
- endpoint: exportValue,
18
- file
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)) endpoints.push({
17
- name: exportName,
18
- endpoint: exportValue,
19
- file
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
  }
@@ -1,3 +1,3 @@
1
- const require_loadEndpoints = require('./loadEndpoints-CYFwuPZr.cjs');
1
+ const require_loadEndpoints = require('./loadEndpoints-Dh-dSqZX.cjs');
2
2
 
3
3
  exports.loadEndpoints = require_loadEndpoints.loadEndpoints;
@@ -1,3 +1,3 @@
1
- import { loadEndpoints } from "./loadEndpoints-BL8q2rTO.mjs";
1
+ import { loadEndpoints } from "./loadEndpoints-DKaw6Eqm.mjs";
2
2
 
3
3
  export { loadEndpoints };
@@ -1,5 +1,5 @@
1
1
  import { loadConfig } from "./config-DV1Lwdkx.mjs";
2
- import { loadEndpoints } from "./loadEndpoints-BL8q2rTO.mjs";
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-CYFwuPZr.cjs');
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 };
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ const require_openapi_react_query = require('./openapi-react-query-BWpdwBix.cjs');
3
+
4
+ exports.generateReactQueryCommand = require_openapi_react_query.generateReactQueryCommand;
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ import { generateReactQueryCommand } from "./openapi-react-query-DuNQ8DwF.mjs";
3
+
4
+ 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-CYFwuPZr.cjs');
4
- const require_openapi = require('./openapi-BX7ba0w0.cjs');
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-BL8q2rTO.mjs";
4
- import { openapiCommand } from "./openapi-BxI6zE0N.mjs";
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.11",
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.25"
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();
@@ -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 as any)) {
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
+ }