@geekmidas/cli 0.10.0 → 0.13.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 +525 -0
- package/dist/bundler-B1qy9b-j.cjs +112 -0
- package/dist/bundler-B1qy9b-j.cjs.map +1 -0
- package/dist/bundler-DskIqW2t.mjs +111 -0
- package/dist/bundler-DskIqW2t.mjs.map +1 -0
- package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
- package/dist/config-AmInkU7k.cjs.map +1 -0
- package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
- package/dist/config-DYULeEv8.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +1 -1
- package/dist/encryption-C8H-38Yy.mjs +42 -0
- package/dist/encryption-C8H-38Yy.mjs.map +1 -0
- package/dist/encryption-Dyf_r1h-.cjs +44 -0
- package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
- package/dist/index.cjs +2123 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2141 -192
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
- package/dist/openapi-BfFlOBCG.mjs.map +1 -0
- package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
- package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
- package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
- package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
- package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
- package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.d.cts.map +1 -1
- package/dist/openapi-react-query.d.mts.map +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/storage-BOOpAF8N.cjs +5 -0
- package/dist/storage-Bj1E26lU.cjs +187 -0
- package/dist/storage-Bj1E26lU.cjs.map +1 -0
- package/dist/storage-kSxTjkNb.mjs +133 -0
- package/dist/storage-kSxTjkNb.mjs.map +1 -0
- package/dist/storage-tgZSUnKl.mjs +3 -0
- package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
- package/dist/types-BR0M2v_c.d.mts.map +1 -0
- package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
- package/dist/types-BhkZc-vm.d.cts.map +1 -0
- package/examples/cron-example.ts +27 -27
- package/examples/env.ts +27 -27
- package/examples/function-example.ts +31 -31
- package/examples/gkm.config.json +20 -20
- package/examples/gkm.config.ts +8 -8
- package/examples/gkm.minimal.config.json +5 -5
- package/examples/gkm.production.config.json +25 -25
- package/examples/logger.ts +2 -2
- package/package.json +6 -6
- package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
- package/src/__tests__/config.spec.ts +55 -55
- package/src/__tests__/loadEnvFiles.spec.ts +93 -93
- package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
- package/src/__tests__/openapi-react-query.spec.ts +497 -497
- package/src/__tests__/openapi.spec.ts +428 -428
- package/src/__tests__/test-helpers.ts +76 -76
- package/src/auth/__tests__/credentials.spec.ts +204 -0
- package/src/auth/__tests__/index.spec.ts +168 -0
- package/src/auth/credentials.ts +187 -0
- package/src/auth/index.ts +226 -0
- package/src/build/__tests__/bundler.spec.ts +444 -0
- package/src/build/__tests__/index-new.spec.ts +474 -474
- package/src/build/__tests__/manifests.spec.ts +333 -333
- package/src/build/bundler.ts +210 -0
- package/src/build/endpoint-analyzer.ts +236 -0
- package/src/build/handler-templates.ts +1253 -0
- package/src/build/index.ts +260 -179
- package/src/build/manifests.ts +52 -52
- package/src/build/providerResolver.ts +145 -145
- package/src/build/types.ts +64 -43
- package/src/config.ts +39 -39
- package/src/deploy/__tests__/docker.spec.ts +111 -0
- package/src/deploy/__tests__/dokploy.spec.ts +245 -0
- package/src/deploy/__tests__/init.spec.ts +662 -0
- package/src/deploy/docker.ts +128 -0
- package/src/deploy/dokploy.ts +204 -0
- package/src/deploy/index.ts +136 -0
- package/src/deploy/init.ts +484 -0
- package/src/deploy/types.ts +48 -0
- package/src/dev/__tests__/index.spec.ts +266 -266
- package/src/dev/index.ts +647 -601
- package/src/docker/__tests__/compose.spec.ts +531 -0
- package/src/docker/__tests__/templates.spec.ts +280 -0
- package/src/docker/compose.ts +273 -0
- package/src/docker/index.ts +230 -0
- package/src/docker/templates.ts +446 -0
- package/src/generators/CronGenerator.ts +72 -72
- package/src/generators/EndpointGenerator.ts +699 -398
- package/src/generators/FunctionGenerator.ts +84 -84
- package/src/generators/Generator.ts +72 -72
- package/src/generators/OpenApiTsGenerator.ts +577 -577
- package/src/generators/SubscriberGenerator.ts +124 -124
- package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
- package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
- package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
- package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
- package/src/generators/index.ts +4 -4
- package/src/index.ts +623 -201
- package/src/init/__tests__/generators.spec.ts +334 -334
- package/src/init/__tests__/init.spec.ts +332 -332
- package/src/init/__tests__/utils.spec.ts +89 -89
- package/src/init/generators/config.ts +175 -175
- package/src/init/generators/docker.ts +41 -41
- package/src/init/generators/env.ts +72 -72
- package/src/init/generators/index.ts +1 -1
- package/src/init/generators/models.ts +64 -64
- package/src/init/generators/monorepo.ts +161 -161
- package/src/init/generators/package.ts +71 -71
- package/src/init/generators/source.ts +6 -6
- package/src/init/index.ts +203 -208
- package/src/init/templates/api.ts +115 -115
- package/src/init/templates/index.ts +75 -75
- package/src/init/templates/minimal.ts +98 -98
- package/src/init/templates/serverless.ts +89 -89
- package/src/init/templates/worker.ts +98 -98
- package/src/init/utils.ts +54 -56
- package/src/openapi-react-query.ts +194 -194
- package/src/openapi.ts +63 -63
- package/src/secrets/__tests__/encryption.spec.ts +226 -0
- package/src/secrets/__tests__/generator.spec.ts +319 -0
- package/src/secrets/__tests__/index.spec.ts +91 -0
- package/src/secrets/__tests__/storage.spec.ts +611 -0
- package/src/secrets/encryption.ts +91 -0
- package/src/secrets/generator.ts +164 -0
- package/src/secrets/index.ts +383 -0
- package/src/secrets/storage.ts +192 -0
- package/src/secrets/types.ts +53 -0
- package/src/types.ts +295 -176
- package/tsdown.config.ts +11 -8
- package/dist/config-BrkUalUh.mjs.map +0 -1
- package/dist/config-C9aXOHBe.cjs.map +0 -1
- package/dist/openapi-BeHLKcwP.cjs.map +0 -1
- package/dist/openapi-CZLI4QTr.mjs.map +0 -1
- package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
- package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
- package/dist/types-DXgiA1sF.d.mts.map +0 -1
- package/dist/types-b-vwGpqc.d.cts.map +0 -1
|
@@ -9,64 +9,64 @@ import { promisify } from 'node:util';
|
|
|
9
9
|
const execAsync = promisify(exec);
|
|
10
10
|
|
|
11
11
|
interface ReactQueryOptions {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
input?: string;
|
|
13
|
+
output?: string;
|
|
14
|
+
name?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
interface OpenAPISpec {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
openapi: string;
|
|
19
|
+
info?: {
|
|
20
|
+
title?: string;
|
|
21
|
+
version?: string;
|
|
22
|
+
};
|
|
23
|
+
paths: Record<string, Record<string, any>>;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export async function generateReactQueryCommand(
|
|
27
|
-
|
|
27
|
+
options: ReactQueryOptions = {},
|
|
28
28
|
): Promise<void> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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(
|
|
37
|
+
`OpenAPI spec not found at ${inputPath}. Run 'npx @geekmidas/cli openapi' first.`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const specContent = await readFile(inputPath, 'utf-8');
|
|
42
|
+
const spec: OpenAPISpec = JSON.parse(specContent);
|
|
43
|
+
|
|
44
|
+
// Generate TypeScript types from OpenAPI spec
|
|
45
|
+
const outputDir = dirname(
|
|
46
|
+
options.output || join(process.cwd(), 'src', 'api', 'hooks.ts'),
|
|
47
|
+
);
|
|
48
|
+
const typesPath = join(outputDir, 'openapi-types.d.ts');
|
|
49
|
+
|
|
50
|
+
logger.log('Generating TypeScript types from OpenAPI spec...');
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// Use npx to run openapi-typescript
|
|
54
|
+
await execAsync(
|
|
55
|
+
`npx openapi-typescript "${inputPath}" -o "${typesPath}"`,
|
|
56
|
+
{ cwd: process.cwd() },
|
|
57
|
+
);
|
|
58
|
+
logger.log(`TypeScript types generated: ${typesPath}`);
|
|
59
|
+
} catch (_error) {
|
|
60
|
+
logger.warn(
|
|
61
|
+
'Could not generate types with openapi-typescript. Install it for better type inference.',
|
|
62
|
+
);
|
|
63
|
+
logger.warn('Run: npm install -D openapi-typescript');
|
|
64
|
+
|
|
65
|
+
// Generate basic types file
|
|
66
|
+
await mkdir(dirname(typesPath), { recursive: true });
|
|
67
|
+
await writeFile(
|
|
68
|
+
typesPath,
|
|
69
|
+
`// Auto-generated placeholder types
|
|
70
70
|
export interface paths {
|
|
71
71
|
[path: string]: {
|
|
72
72
|
[method: string]: {
|
|
@@ -78,115 +78,115 @@ export interface paths {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
`,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Extract operation info
|
|
85
|
+
const operations = extractOperations(spec);
|
|
86
|
+
|
|
87
|
+
// Generate TypeScript code
|
|
88
|
+
const code = generateReactQueryCode(
|
|
89
|
+
spec,
|
|
90
|
+
operations,
|
|
91
|
+
options.name || 'API',
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Write output
|
|
95
|
+
const outputPath =
|
|
96
|
+
options.output || join(process.cwd(), 'src', 'api', 'hooks.ts');
|
|
97
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
98
|
+
await writeFile(outputPath, code);
|
|
99
|
+
|
|
100
|
+
logger.log(`React Query hooks generated: ${outputPath}`);
|
|
101
|
+
logger.log(`Generated ${operations.length} hooks`);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`React Query generation failed: ${(error as Error).message}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
interface OperationInfo {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
operationId: string;
|
|
111
|
+
path: string;
|
|
112
|
+
method: string;
|
|
113
|
+
endpoint: string; // Full endpoint like 'GET /users/{id}'
|
|
114
|
+
parameters?: Array<{ name: string; in: string; required?: boolean }>;
|
|
115
|
+
requestBody?: boolean;
|
|
116
|
+
responseType?: string;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function extractOperations(spec: OpenAPISpec): OperationInfo[] {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
120
|
+
const operations: OperationInfo[] = [];
|
|
121
|
+
|
|
122
|
+
Object.entries(spec.paths).forEach(([path, methods]) => {
|
|
123
|
+
Object.entries(methods).forEach(([method, operation]) => {
|
|
124
|
+
if (operation.operationId) {
|
|
125
|
+
operations.push({
|
|
126
|
+
operationId: operation.operationId,
|
|
127
|
+
path,
|
|
128
|
+
method: method.toUpperCase(),
|
|
129
|
+
endpoint: `${method.toUpperCase()} ${path}`,
|
|
130
|
+
parameters: operation.parameters,
|
|
131
|
+
requestBody: !!operation.requestBody,
|
|
132
|
+
responseType: extractResponseType(operation),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return operations;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
function extractResponseType(operation: any): string {
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
const responses = operation.responses;
|
|
143
|
+
if (!responses) return 'unknown';
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
const successResponse = responses['200'] || responses['201'];
|
|
146
|
+
if (!successResponse?.content?.['application/json']?.schema) {
|
|
147
|
+
return 'unknown';
|
|
148
|
+
}
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
// Basic type inference from schema
|
|
151
|
+
const schema = successResponse.content['application/json'].schema;
|
|
152
|
+
return schemaToTypeString(schema);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
function schemaToTypeString(schema: any): string {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
156
|
+
if (!schema) return 'unknown';
|
|
157
|
+
|
|
158
|
+
switch (schema.type) {
|
|
159
|
+
case 'string':
|
|
160
|
+
return 'string';
|
|
161
|
+
case 'number':
|
|
162
|
+
case 'integer':
|
|
163
|
+
return 'number';
|
|
164
|
+
case 'boolean':
|
|
165
|
+
return 'boolean';
|
|
166
|
+
case 'array':
|
|
167
|
+
return `Array<${schemaToTypeString(schema.items)}>`;
|
|
168
|
+
case 'object':
|
|
169
|
+
if (schema.properties) {
|
|
170
|
+
const props = Object.entries(schema.properties)
|
|
171
|
+
.map(
|
|
172
|
+
([key, value]: [string, any]) =>
|
|
173
|
+
`${key}: ${schemaToTypeString(value)}`,
|
|
174
|
+
)
|
|
175
|
+
.join('; ');
|
|
176
|
+
return `{ ${props} }`;
|
|
177
|
+
}
|
|
178
|
+
return 'Record<string, unknown>';
|
|
179
|
+
default:
|
|
180
|
+
return 'unknown';
|
|
181
|
+
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
function generateReactQueryCode(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
_spec: OpenAPISpec,
|
|
186
|
+
operations: OperationInfo[],
|
|
187
|
+
apiName: string,
|
|
188
188
|
): string {
|
|
189
|
-
|
|
189
|
+
const imports = `import { createTypedQueryClient } from '@geekmidas/client';
|
|
190
190
|
import type { paths } from './openapi-types';
|
|
191
191
|
|
|
192
192
|
// Create typed query client
|
|
@@ -197,19 +197,19 @@ export const ${apiName.toLowerCase()} = createTypedQueryClient<paths>({
|
|
|
197
197
|
// Export individual hooks for better DX
|
|
198
198
|
`;
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
const queryHooks = operations
|
|
201
|
+
.filter((op) => op.method === 'GET')
|
|
202
|
+
.map((op) => generateQueryHook(op, apiName))
|
|
203
|
+
.join('\n\n');
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
const mutationHooks = operations
|
|
206
|
+
.filter((op) => op.method !== 'GET')
|
|
207
|
+
.map((op) => generateMutationHook(op, apiName))
|
|
208
|
+
.join('\n\n');
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
const typeExports = generateTypeExports(operations);
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
return `${imports}
|
|
213
213
|
// Query Hooks
|
|
214
214
|
${queryHooks}
|
|
215
215
|
|
|
@@ -225,32 +225,32 @@ export { ${apiName.toLowerCase()} };
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
function generateQueryHook(op: OperationInfo, apiName: string): string {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
228
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
229
|
+
const endpoint = op.endpoint;
|
|
230
|
+
const hasParams = op.parameters?.some((p) => p.in === 'path');
|
|
231
|
+
const hasQuery = op.parameters?.some((p) => p.in === 'query');
|
|
232
|
+
|
|
233
|
+
// Generate properly typed hook
|
|
234
|
+
let params = '';
|
|
235
|
+
let args = '';
|
|
236
|
+
|
|
237
|
+
if (hasParams || hasQuery) {
|
|
238
|
+
const paramParts: string[] = [];
|
|
239
|
+
if (hasParams) {
|
|
240
|
+
const pathParams =
|
|
241
|
+
op.parameters?.filter((p) => p.in === 'path').map((p) => p.name) || [];
|
|
242
|
+
paramParts.push(
|
|
243
|
+
`params: { ${pathParams.map((p) => `${p}: string`).join('; ')} }`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (hasQuery) {
|
|
247
|
+
paramParts.push(`query?: Record<string, any>`);
|
|
248
|
+
}
|
|
249
|
+
params = `config: { ${paramParts.join('; ')} }, `;
|
|
250
|
+
args = ', config';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return `export const ${hookName} = (
|
|
254
254
|
${params}options?: Parameters<typeof ${apiName.toLowerCase()}.useQuery>[2]
|
|
255
255
|
) => {
|
|
256
256
|
return ${apiName.toLowerCase()}.useQuery('${endpoint}' as any${args}, options);
|
|
@@ -258,10 +258,10 @@ function generateQueryHook(op: OperationInfo, apiName: string): string {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
function generateMutationHook(op: OperationInfo, apiName: string): string {
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
const hookName = `use${capitalize(op.operationId)}`;
|
|
262
|
+
const endpoint = op.endpoint;
|
|
263
263
|
|
|
264
|
-
|
|
264
|
+
return `export const ${hookName} = (
|
|
265
265
|
options?: Parameters<typeof ${apiName.toLowerCase()}.useMutation>[1]
|
|
266
266
|
) => {
|
|
267
267
|
return ${apiName.toLowerCase()}.useMutation('${endpoint}' as any, options);
|
|
@@ -269,20 +269,20 @@ function generateMutationHook(op: OperationInfo, apiName: string): string {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
function generateTypeExports(operations: OperationInfo[]): string {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
272
|
+
const exports = operations.map((op) => {
|
|
273
|
+
const typeName = capitalize(op.operationId);
|
|
274
|
+
const isQuery = op.method === 'GET';
|
|
275
|
+
|
|
276
|
+
if (isQuery) {
|
|
277
|
+
return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['data']>>;`;
|
|
278
|
+
} else {
|
|
279
|
+
return `export type ${typeName}Response = Awaited<ReturnType<ReturnType<typeof use${typeName}>['mutateAsync']>>;`;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return exports.join('\n');
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
function capitalize(str: string): string {
|
|
287
|
-
|
|
287
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
288
288
|
}
|
package/src/openapi.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { OpenApiTsGenerator } from './generators/OpenApiTsGenerator.js';
|
|
|
8
8
|
import type { GkmConfig, OpenApiConfig } from './types.js';
|
|
9
9
|
|
|
10
10
|
interface OpenAPIOptions {
|
|
11
|
-
|
|
11
|
+
cwd?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -20,29 +20,29 @@ export const OPENAPI_OUTPUT_PATH = './.gkm/openapi.ts';
|
|
|
20
20
|
* Resolve OpenAPI config from GkmConfig
|
|
21
21
|
*/
|
|
22
22
|
export function resolveOpenApiConfig(
|
|
23
|
-
|
|
23
|
+
config: GkmConfig,
|
|
24
24
|
): OpenApiConfig & { enabled: boolean } {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
if (config.openapi === false) {
|
|
26
|
+
return { enabled: false };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (config.openapi === true || config.openapi === undefined) {
|
|
30
|
+
return {
|
|
31
|
+
enabled: config.openapi === true,
|
|
32
|
+
title: 'API Documentation',
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
description: 'Auto-generated API documentation from endpoints',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
enabled: config.openapi.enabled !== false,
|
|
40
|
+
title: config.openapi.title || 'API Documentation',
|
|
41
|
+
version: config.openapi.version || '1.0.0',
|
|
42
|
+
description:
|
|
43
|
+
config.openapi.description ||
|
|
44
|
+
'Auto-generated API documentation from endpoints',
|
|
45
|
+
};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -50,61 +50,61 @@ export function resolveOpenApiConfig(
|
|
|
50
50
|
* @returns Object with output path and endpoint count, or null if disabled
|
|
51
51
|
*/
|
|
52
52
|
export async function generateOpenApi(
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
config: GkmConfig,
|
|
54
|
+
options: { silent?: boolean } = {},
|
|
55
55
|
): Promise<{ outputPath: string; endpointCount: number } | null> {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const logger = options.silent ? { log: () => {} } : console;
|
|
57
|
+
const openApiConfig = resolveOpenApiConfig(config);
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
if (!openApiConfig.enabled) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const endpointGenerator = new EndpointGenerator();
|
|
64
|
+
const loadedEndpoints = await endpointGenerator.load(config.routes);
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
if (loadedEndpoints.length === 0) {
|
|
67
|
+
logger.log('No valid endpoints found for OpenAPI generation');
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const endpoints = loadedEndpoints.map(({ construct }) => construct);
|
|
72
|
+
const outputPath = join(process.cwd(), OPENAPI_OUTPUT_PATH);
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
const tsGenerator = new OpenApiTsGenerator();
|
|
77
|
+
const tsContent = await tsGenerator.generate(endpoints, {
|
|
78
|
+
title: openApiConfig.title!,
|
|
79
|
+
version: openApiConfig.version!,
|
|
80
|
+
description: openApiConfig.description!,
|
|
81
|
+
});
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
await writeFile(outputPath, tsContent);
|
|
84
|
+
logger.log(`📄 OpenAPI client generated: ${OPENAPI_OUTPUT_PATH}`);
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
return { outputPath, endpointCount: loadedEndpoints.length };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export async function openapiCommand(
|
|
90
|
-
|
|
90
|
+
options: OpenAPIOptions = {},
|
|
91
91
|
): Promise<void> {
|
|
92
|
-
|
|
92
|
+
const logger = console;
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
try {
|
|
95
|
+
const config = await loadConfig(options.cwd);
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
// Enable openapi if not configured
|
|
98
|
+
if (!config.openapi) {
|
|
99
|
+
config.openapi = { enabled: true };
|
|
100
|
+
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
const result = await generateOpenApi(config);
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
if (result) {
|
|
105
|
+
logger.log(`Found ${result.endpointCount} endpoints`);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new Error(`OpenAPI generation failed: ${(error as Error).message}`);
|
|
109
|
+
}
|
|
110
110
|
}
|