@geekmidas/cli 0.0.15 → 0.0.17
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-DqTE4qtW.mjs +160 -0
- package/dist/{build-CTKUZTHx.cjs → build-HWB991oI.cjs} +2 -2
- package/dist/build.cjs +3 -3
- package/dist/build.mjs +5 -0
- package/dist/{config-D8AyiwBU.cjs → config-BNqUMsvc.cjs} +4 -3
- package/dist/config-BciAdY6_.mjs +18 -0
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +3 -0
- package/dist/{cli.cjs → index.cjs} +8 -8
- package/dist/index.mjs +94 -0
- package/dist/{loadEndpoints-Dh-dSqZX.cjs → loadEndpoints-BBIavB9h.cjs} +1 -0
- package/dist/loadEndpoints-DAZ53Og2.mjs +31 -0
- package/dist/loadEndpoints.cjs +1 -1
- package/dist/loadEndpoints.mjs +3 -0
- package/dist/openapi-CksVdkh2.mjs +34 -0
- package/dist/{openapi-DrPYAlJ_.cjs → openapi-D4QQJUPY.cjs} +2 -2
- package/dist/openapi-react-query-DpT3XHFC.mjs +165 -0
- package/dist/openapi-react-query.mjs +4 -0
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.mjs +6 -0
- package/dist/types.mjs +0 -0
- package/package.json +3 -3
- package/src/config.ts +5 -4
- package/src/loadEndpoints.ts +3 -0
- package/tsdown.config.ts +1 -1
- /package/src/{cli.ts → index.ts} +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { loadConfig } from "./config-BciAdY6_.mjs";
|
|
2
|
+
import { loadEndpoints } from "./loadEndpoints-DAZ53Og2.mjs";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, relative } from "path";
|
|
5
|
+
|
|
6
|
+
//#region src/build.ts
|
|
7
|
+
const logger = console;
|
|
8
|
+
async function buildCommand(options) {
|
|
9
|
+
logger.log(`Building with providers: ${options.providers.join(", ")}`);
|
|
10
|
+
const config = await loadConfig();
|
|
11
|
+
logger.log(`Loading routes from: ${config.routes}`);
|
|
12
|
+
logger.log(`Using envParser: ${config.envParser}`);
|
|
13
|
+
const [envParserPath, envParserName] = config.envParser.split("#");
|
|
14
|
+
const envParserImportPattern = !envParserName ? "envParser" : envParserName === "envParser" ? "{ envParser }" : `{ ${envParserName} as envParser }`;
|
|
15
|
+
const [loggerPath, loggerName] = config.logger.split("#");
|
|
16
|
+
const loggerImportPattern = !loggerName ? "logger" : loggerName === "logger" ? "{ logger }" : `{ ${loggerName} as logger }`;
|
|
17
|
+
const loadedEndpoints = await loadEndpoints(config.routes);
|
|
18
|
+
if (loadedEndpoints.length === 0) {
|
|
19
|
+
logger.log("No endpoints found to process");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const allEndpoints = loadedEndpoints.map(({ name, endpoint, file }) => {
|
|
23
|
+
const routeInfo = {
|
|
24
|
+
path: endpoint._path,
|
|
25
|
+
method: endpoint.method,
|
|
26
|
+
handler: ""
|
|
27
|
+
};
|
|
28
|
+
logger.log(`Found endpoint: ${name} - ${routeInfo.method} ${routeInfo.path}`);
|
|
29
|
+
return {
|
|
30
|
+
file: relative(process.cwd(), file),
|
|
31
|
+
exportName: name,
|
|
32
|
+
endpoint,
|
|
33
|
+
routeInfo
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
for (const provider of options.providers) {
|
|
37
|
+
const routes = [];
|
|
38
|
+
const outputDir = join(process.cwd(), ".gkm", provider);
|
|
39
|
+
await mkdir(outputDir, { recursive: true });
|
|
40
|
+
logger.log(`\nGenerating handlers for provider: ${provider}`);
|
|
41
|
+
if (provider === "server") {
|
|
42
|
+
const serverFile = await generateServerFile(outputDir, allEndpoints, envParserPath, envParserImportPattern, loggerPath, loggerImportPattern);
|
|
43
|
+
routes.push({
|
|
44
|
+
path: "*",
|
|
45
|
+
method: "ALL",
|
|
46
|
+
handler: relative(process.cwd(), serverFile)
|
|
47
|
+
});
|
|
48
|
+
logger.log(`Generated server app with ${allEndpoints.length} endpoints`);
|
|
49
|
+
} else for (const { file, exportName, routeInfo } of allEndpoints) {
|
|
50
|
+
const handlerFile = await generateHandlerFile(outputDir, file, exportName, provider, routeInfo, envParserPath, envParserImportPattern);
|
|
51
|
+
routes.push({
|
|
52
|
+
...routeInfo,
|
|
53
|
+
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler")
|
|
54
|
+
});
|
|
55
|
+
logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
|
|
56
|
+
}
|
|
57
|
+
const manifest = { routes };
|
|
58
|
+
const manifestPath = join(outputDir, "routes.json");
|
|
59
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
60
|
+
logger.log(`Generated ${routes.length} handlers in ${relative(process.cwd(), outputDir)}`);
|
|
61
|
+
logger.log(`Routes manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function generateServerFile(outputDir, endpoints, envParserPath, envParserImportPattern, loggerPath, loggerImportPattern) {
|
|
65
|
+
const serverFileName = "app.ts";
|
|
66
|
+
const serverPath = join(outputDir, serverFileName);
|
|
67
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
68
|
+
for (const { file, exportName } of endpoints) {
|
|
69
|
+
const relativePath = relative(dirname(serverPath), file);
|
|
70
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
71
|
+
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
72
|
+
importsByFile.get(importPath).push(exportName);
|
|
73
|
+
}
|
|
74
|
+
const relativeEnvParserPath = relative(dirname(serverPath), envParserPath);
|
|
75
|
+
const relativeLoggerPath = relative(dirname(serverPath), loggerPath);
|
|
76
|
+
const imports = Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n");
|
|
77
|
+
const allExportNames = endpoints.map(({ exportName }) => exportName);
|
|
78
|
+
const content = `import { HonoEndpoint } from '@geekmidas/api/hono';
|
|
79
|
+
import { Endpoint } from '@geekmidas/api/server';
|
|
80
|
+
import { ServiceDiscovery } from '@geekmidas/api/services';
|
|
81
|
+
import { Hono } from 'hono';
|
|
82
|
+
import ${envParserImportPattern} from '${relativeEnvParserPath}';
|
|
83
|
+
import ${loggerImportPattern} from '${relativeLoggerPath}';
|
|
84
|
+
${imports}
|
|
85
|
+
|
|
86
|
+
export function createApp(app?: Hono): Hono {
|
|
87
|
+
const honoApp = app || new Hono();
|
|
88
|
+
|
|
89
|
+
const endpoints: Endpoint<any, any, any, any, any, any>[] = [
|
|
90
|
+
${allExportNames.join(",\n ")}
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const serviceDiscovery = ServiceDiscovery.getInstance(
|
|
94
|
+
logger,
|
|
95
|
+
envParser
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
HonoEndpoint.addRoutes(endpoints, serviceDiscovery, honoApp);
|
|
99
|
+
|
|
100
|
+
return honoApp;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Default export for convenience
|
|
104
|
+
export default createApp;
|
|
105
|
+
`;
|
|
106
|
+
await writeFile(serverPath, content);
|
|
107
|
+
return serverPath;
|
|
108
|
+
}
|
|
109
|
+
async function generateHandlerFile(outputDir, sourceFile, exportName, provider, _routeInfo, envParserPath, envParserImportPattern) {
|
|
110
|
+
const handlerFileName = `${exportName}.ts`;
|
|
111
|
+
const handlerPath = join(outputDir, handlerFileName);
|
|
112
|
+
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
113
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
114
|
+
const relativeEnvParserPath = relative(dirname(handlerPath), envParserPath);
|
|
115
|
+
let content;
|
|
116
|
+
switch (provider) {
|
|
117
|
+
case "aws-apigatewayv1":
|
|
118
|
+
content = generateAWSApiGatewayV1Handler(importPath, exportName, relativeEnvParserPath, envParserImportPattern);
|
|
119
|
+
break;
|
|
120
|
+
case "aws-apigatewayv2":
|
|
121
|
+
content = generateAWSApiGatewayV2Handler(importPath, exportName, relativeEnvParserPath, envParserImportPattern);
|
|
122
|
+
break;
|
|
123
|
+
case "server":
|
|
124
|
+
content = generateServerHandler(importPath, exportName);
|
|
125
|
+
break;
|
|
126
|
+
default: throw new Error(`Unsupported provider: ${provider}`);
|
|
127
|
+
}
|
|
128
|
+
await writeFile(handlerPath, content);
|
|
129
|
+
return handlerPath;
|
|
130
|
+
}
|
|
131
|
+
function generateAWSApiGatewayV1Handler(importPath, exportName, envParserPath, envParserImportPattern) {
|
|
132
|
+
return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
133
|
+
import { ${exportName} } from '${importPath}';
|
|
134
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
135
|
+
|
|
136
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, ${exportName});
|
|
137
|
+
|
|
138
|
+
export const handler = adapter.handler;
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
function generateAWSApiGatewayV2Handler(importPath, exportName, envParserPath, envParserImportPattern) {
|
|
142
|
+
return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
143
|
+
import { ${exportName} } from '${importPath}';
|
|
144
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
145
|
+
|
|
146
|
+
const adapter = new AmazonApiGatewayV2Endpoint(envParser, ${exportName});
|
|
147
|
+
|
|
148
|
+
export const handler = adapter.handler;
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
function generateServerHandler(importPath, exportName) {
|
|
152
|
+
return `import { ${exportName} } from '${importPath}';
|
|
153
|
+
|
|
154
|
+
// Server handler - implement based on your server framework
|
|
155
|
+
export const handler = ${exportName};
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
export { buildCommand };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
-
const require_config = require('./config-
|
|
3
|
-
const require_loadEndpoints = require('./loadEndpoints-
|
|
2
|
+
const require_config = require('./config-BNqUMsvc.cjs');
|
|
3
|
+
const require_loadEndpoints = require('./loadEndpoints-BBIavB9h.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
|
|
package/dist/build.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require('./config-
|
|
2
|
-
require('./loadEndpoints-
|
|
3
|
-
const require_build = require('./build-
|
|
1
|
+
require('./config-BNqUMsvc.cjs');
|
|
2
|
+
require('./loadEndpoints-BBIavB9h.cjs');
|
|
3
|
+
const require_build = require('./build-HWB991oI.cjs');
|
|
4
4
|
|
|
5
5
|
exports.buildCommand = require_build.buildCommand;
|
package/dist/build.mjs
ADDED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
2
|
const path = require_chunk.__toESM(require("path"));
|
|
3
3
|
const fs = require_chunk.__toESM(require("fs"));
|
|
4
|
+
const fs_promises = require_chunk.__toESM(require("fs/promises"));
|
|
4
5
|
|
|
5
6
|
//#region src/config.ts
|
|
6
7
|
async function loadConfig() {
|
|
7
8
|
const configPath = (0, path.join)(process.cwd(), "gkm.config.json");
|
|
8
9
|
if (!(0, fs.existsSync)(configPath)) throw new Error("gkm.config.json not found. Please create a configuration file.");
|
|
9
10
|
try {
|
|
10
|
-
const config = await
|
|
11
|
-
return
|
|
11
|
+
const config = await (0, fs_promises.readFile)(configPath, "utf-8");
|
|
12
|
+
return JSON.parse(config);
|
|
12
13
|
} catch (error) {
|
|
13
|
-
throw new Error(`Failed to load gkm.config.
|
|
14
|
+
throw new Error(`Failed to load gkm.config.json: ${error.message}`);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
|
|
5
|
+
//#region src/config.ts
|
|
6
|
+
async function loadConfig() {
|
|
7
|
+
const configPath = join(process.cwd(), "gkm.config.json");
|
|
8
|
+
if (!existsSync(configPath)) throw new Error("gkm.config.json not found. Please create a configuration file.");
|
|
9
|
+
try {
|
|
10
|
+
const config = await readFile(configPath, "utf-8");
|
|
11
|
+
return JSON.parse(config);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Failed to load gkm.config.json: ${error.message}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { loadConfig };
|
package/dist/config.cjs
CHANGED
package/dist/config.mjs
ADDED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
-
require('./config-
|
|
4
|
-
require('./loadEndpoints-
|
|
5
|
-
const require_build = require('./build-
|
|
3
|
+
require('./config-BNqUMsvc.cjs');
|
|
4
|
+
require('./loadEndpoints-BBIavB9h.cjs');
|
|
5
|
+
const require_build = require('./build-HWB991oI.cjs');
|
|
6
6
|
const require_openapi_react_query = require('./openapi-react-query-C1JLYUOs.cjs');
|
|
7
|
-
const require_openapi = require('./openapi-
|
|
7
|
+
const require_openapi = require('./openapi-D4QQJUPY.cjs');
|
|
8
8
|
const commander = require_chunk.__toESM(require("commander"));
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
11
|
var name = "@geekmidas/cli";
|
|
12
|
-
var version = "0.0.
|
|
12
|
+
var version = "0.0.17";
|
|
13
13
|
var private$1 = false;
|
|
14
|
-
var type = "
|
|
15
|
-
var bin = { "gkm": "./dist/
|
|
14
|
+
var type = "module";
|
|
15
|
+
var bin = { "gkm": "./dist/index.cjs" };
|
|
16
16
|
var publishConfig = {
|
|
17
17
|
"registry": "https://registry.npmjs.org/",
|
|
18
18
|
"access": "public"
|
|
@@ -41,7 +41,7 @@ var package_default = {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
|
-
//#region src/
|
|
44
|
+
//#region src/index.ts
|
|
45
45
|
const program = new commander.Command();
|
|
46
46
|
program.name("gkm").description("GeekMidas backend framework CLI").version(package_default.version).option("--cwd <path>", "Change working directory");
|
|
47
47
|
program.command("build").description("Build API handlers from endpoints").option("--providers <providers>", "Target providers for generated handlers (comma-separated)", "aws-apigatewayv1").action(async (options) => {
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
import "./config-BciAdY6_.mjs";
|
|
3
|
+
import "./loadEndpoints-DAZ53Og2.mjs";
|
|
4
|
+
import { buildCommand } from "./build-DqTE4qtW.mjs";
|
|
5
|
+
import { generateReactQueryCommand } from "./openapi-react-query-DpT3XHFC.mjs";
|
|
6
|
+
import { openapiCommand } from "./openapi-CksVdkh2.mjs";
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
//#region package.json
|
|
10
|
+
var name = "@geekmidas/cli";
|
|
11
|
+
var version = "0.0.17";
|
|
12
|
+
var private$1 = false;
|
|
13
|
+
var type = "module";
|
|
14
|
+
var bin = { "gkm": "./dist/index.cjs" };
|
|
15
|
+
var publishConfig = {
|
|
16
|
+
"registry": "https://registry.npmjs.org/",
|
|
17
|
+
"access": "public"
|
|
18
|
+
};
|
|
19
|
+
var dependencies = {
|
|
20
|
+
"commander": "~14.0.0",
|
|
21
|
+
"lodash.get": "~4.4.2",
|
|
22
|
+
"lodash.set": "~4.3.2",
|
|
23
|
+
"zod": "~3.25.67",
|
|
24
|
+
"fast-glob": "~3.3.3",
|
|
25
|
+
"@geekmidas/api": "workspace:*"
|
|
26
|
+
};
|
|
27
|
+
var devDependencies = {
|
|
28
|
+
"@types/lodash.get": "~4.4.9",
|
|
29
|
+
"@types/lodash.set": "~4.3.9"
|
|
30
|
+
};
|
|
31
|
+
var package_default = {
|
|
32
|
+
name,
|
|
33
|
+
version,
|
|
34
|
+
private: private$1,
|
|
35
|
+
type,
|
|
36
|
+
bin,
|
|
37
|
+
publishConfig,
|
|
38
|
+
dependencies,
|
|
39
|
+
devDependencies
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/index.ts
|
|
44
|
+
const program = new Command();
|
|
45
|
+
program.name("gkm").description("GeekMidas backend framework CLI").version(package_default.version).option("--cwd <path>", "Change working directory");
|
|
46
|
+
program.command("build").description("Build API handlers from endpoints").option("--providers <providers>", "Target providers for generated handlers (comma-separated)", "aws-apigatewayv1").action(async (options) => {
|
|
47
|
+
try {
|
|
48
|
+
const globalOptions = program.opts();
|
|
49
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
50
|
+
const providerList = [...new Set(options.providers.split(",").map((p) => p.trim()))];
|
|
51
|
+
await buildCommand({ providers: providerList });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("Build failed:", error.message);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
program.command("cron").description("Manage cron jobs").action(() => {
|
|
58
|
+
const globalOptions = program.opts();
|
|
59
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
60
|
+
process.stdout.write("Cron management - coming soon\n");
|
|
61
|
+
});
|
|
62
|
+
program.command("function").description("Manage serverless functions").action(() => {
|
|
63
|
+
const globalOptions = program.opts();
|
|
64
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
65
|
+
process.stdout.write("Serverless function management - coming soon\n");
|
|
66
|
+
});
|
|
67
|
+
program.command("api").description("Manage REST API endpoints").action(() => {
|
|
68
|
+
const globalOptions = program.opts();
|
|
69
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
70
|
+
process.stdout.write("REST API management - coming soon\n");
|
|
71
|
+
});
|
|
72
|
+
program.command("openapi").description("Generate OpenAPI 3.0 specification from endpoints").option("--output <path>", "Output file path for the OpenAPI spec", "openapi.json").action(async (options) => {
|
|
73
|
+
try {
|
|
74
|
+
const globalOptions = program.opts();
|
|
75
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
76
|
+
await openapiCommand(options);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error("OpenAPI generation failed:", error.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
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) => {
|
|
83
|
+
try {
|
|
84
|
+
const globalOptions = program.opts();
|
|
85
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
86
|
+
await generateReactQueryCommand(options);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("React Query generation failed:", error.message);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
program.parse();
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Endpoint } from "@geekmidas/api/server";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
|
|
4
|
+
//#region src/loadEndpoints.ts
|
|
5
|
+
async function loadEndpoints(routes) {
|
|
6
|
+
const logger = console;
|
|
7
|
+
const files = await fg.stream(routes, {
|
|
8
|
+
cwd: process.cwd(),
|
|
9
|
+
absolute: true
|
|
10
|
+
});
|
|
11
|
+
const endpoints = [];
|
|
12
|
+
for await (const f of files) try {
|
|
13
|
+
const file = f.toString();
|
|
14
|
+
const module = await import(file);
|
|
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
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
logger.warn(`Failed to load ${f}:`, error.message);
|
|
25
|
+
throw new Error("Failed to load endpoints. Please check the logs for details.");
|
|
26
|
+
}
|
|
27
|
+
return endpoints;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { loadEndpoints };
|
package/dist/loadEndpoints.cjs
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { loadConfig } from "./config-BciAdY6_.mjs";
|
|
2
|
+
import { loadEndpoints } from "./loadEndpoints-DAZ53Og2.mjs";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { Endpoint } from "@geekmidas/api/server";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
//#region src/openapi.ts
|
|
8
|
+
async function openapiCommand(options = {}) {
|
|
9
|
+
const logger = console;
|
|
10
|
+
try {
|
|
11
|
+
const config = await loadConfig();
|
|
12
|
+
const loadedEndpoints = await loadEndpoints(config.routes);
|
|
13
|
+
if (loadedEndpoints.length === 0) {
|
|
14
|
+
logger.log("No valid endpoints found");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const endpoints = loadedEndpoints.map(({ endpoint }) => endpoint);
|
|
18
|
+
const spec = await Endpoint.buildOpenApiSchema(endpoints, {
|
|
19
|
+
title: "API Documentation",
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
description: "Auto-generated API documentation from endpoints"
|
|
22
|
+
});
|
|
23
|
+
const outputPath = options.output || join(process.cwd(), "openapi.json");
|
|
24
|
+
await mkdir(join(outputPath, ".."), { recursive: true });
|
|
25
|
+
await writeFile(outputPath, JSON.stringify(spec, null, 2));
|
|
26
|
+
logger.log(`OpenAPI spec generated: ${outputPath}`);
|
|
27
|
+
logger.log(`Found ${endpoints.length} endpoints`);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`OpenAPI generation failed: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { openapiCommand };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
-
const require_config = require('./config-
|
|
3
|
-
const require_loadEndpoints = require('./loadEndpoints-
|
|
2
|
+
const require_config = require('./config-BNqUMsvc.cjs');
|
|
3
|
+
const require_loadEndpoints = require('./loadEndpoints-BBIavB9h.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,165 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
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
|
-
require('./config-
|
|
3
|
-
require('./loadEndpoints-
|
|
4
|
-
const require_openapi = require('./openapi-
|
|
2
|
+
require('./config-BNqUMsvc.cjs');
|
|
3
|
+
require('./loadEndpoints-BBIavB9h.cjs');
|
|
4
|
+
const require_openapi = require('./openapi-D4QQJUPY.cjs');
|
|
5
5
|
|
|
6
6
|
exports.openapiCommand = require_openapi.openapiCommand;
|
package/dist/openapi.mjs
ADDED
package/dist/types.mjs
ADDED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"private": false,
|
|
5
|
-
"type": "
|
|
5
|
+
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"gkm": "./dist/
|
|
7
|
+
"gkm": "./dist/index.cjs"
|
|
8
8
|
},
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"registry": "https://registry.npmjs.org/",
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import type { GkmConfig } from './types.ts';
|
|
4
5
|
|
|
5
6
|
export async function loadConfig(): Promise<GkmConfig> {
|
|
6
7
|
const configPath = join(process.cwd(), 'gkm.config.json');
|
|
@@ -12,11 +13,11 @@ export async function loadConfig(): Promise<GkmConfig> {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
try {
|
|
15
|
-
const config = await
|
|
16
|
-
return
|
|
16
|
+
const config = await readFile(configPath, 'utf-8');
|
|
17
|
+
return JSON.parse(config);
|
|
17
18
|
} catch (error) {
|
|
18
19
|
throw new Error(
|
|
19
|
-
`Failed to load gkm.config.
|
|
20
|
+
`Failed to load gkm.config.json: ${(error as Error).message}`,
|
|
20
21
|
);
|
|
21
22
|
}
|
|
22
23
|
}
|
package/src/loadEndpoints.ts
CHANGED
|
@@ -38,6 +38,9 @@ export async function loadEndpoints(routes: Routes): Promise<LoadedEndpoint[]> {
|
|
|
38
38
|
}
|
|
39
39
|
} catch (error) {
|
|
40
40
|
logger.warn(`Failed to load ${f}:`, (error as Error).message);
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Failed to load endpoints. Please check the logs for details.',
|
|
43
|
+
);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
package/tsdown.config.ts
CHANGED
/package/src/{cli.ts → index.ts}
RENAMED
|
File without changes
|