@geekmidas/cli 0.0.1
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-BgAllbWO.mjs +156 -0
- package/dist/build-DN7u1ccT.cjs +162 -0
- package/dist/build.cjs +5 -0
- package/dist/build.mjs +5 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/cli-D6VgkVGJ.mjs +35 -0
- package/dist/cli-eVdV1MSk.cjs +36 -0
- package/dist/cli.cjs +6 -0
- package/dist/cli.mjs +6 -0
- package/dist/config-B-D8cs7V.cjs +23 -0
- package/dist/config-Bzzc89wR.mjs +17 -0
- package/dist/config.cjs +3 -0
- package/dist/config.mjs +3 -0
- package/dist/index.cjs +6 -0
- package/dist/index.mjs +6 -0
- package/dist/loadEndpoints-BL8q2rTO.mjs +27 -0
- package/dist/loadEndpoints-CYFwuPZr.cjs +33 -0
- package/dist/loadEndpoints.cjs +3 -0
- package/dist/loadEndpoints.mjs +3 -0
- package/dist/openapi-BKjcm03Q.cjs +40 -0
- package/dist/openapi-DLix_iqn.mjs +34 -0
- package/dist/openapi.cjs +6 -0
- package/dist/openapi.mjs +6 -0
- package/dist/types.cjs +0 -0
- package/dist/types.mjs +0 -0
- package/examples/env.ts +33 -0
- package/examples/gkm.config.ts +16 -0
- package/examples/logger.ts +10 -0
- package/package.json +32 -0
- package/src/build.ts +283 -0
- package/src/cli.ts +70 -0
- package/src/config.ts +22 -0
- package/src/index.ts +6 -0
- package/src/loadEndpoints.ts +43 -0
- package/src/openapi.ts +50 -0
- package/src/types.ts +21 -0
- package/tsdown.config.ts +3 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { loadConfig } from "./config-Bzzc89wR.mjs";
|
|
2
|
+
import { loadEndpoints } from "./loadEndpoints-BL8q2rTO.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 provider: ${options.provider}`);
|
|
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 routes = [];
|
|
18
|
+
const outputDir = join(process.cwd(), ".gkm", options.provider);
|
|
19
|
+
await mkdir(outputDir, { recursive: true });
|
|
20
|
+
const loadedEndpoints = await loadEndpoints(config.routes);
|
|
21
|
+
if (loadedEndpoints.length === 0) {
|
|
22
|
+
logger.log("No endpoints found to process");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const allEndpoints = loadedEndpoints.map(({ name, endpoint, file }) => {
|
|
26
|
+
const routeInfo = {
|
|
27
|
+
path: endpoint.route,
|
|
28
|
+
method: endpoint.method,
|
|
29
|
+
handler: ""
|
|
30
|
+
};
|
|
31
|
+
logger.log(`Found endpoint: ${name} - ${routeInfo.method} ${routeInfo.path}`);
|
|
32
|
+
return {
|
|
33
|
+
file: relative(process.cwd(), file),
|
|
34
|
+
exportName: name,
|
|
35
|
+
endpoint,
|
|
36
|
+
routeInfo
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
if (options.provider === "server") {
|
|
40
|
+
const serverFile = await generateServerFile(outputDir, allEndpoints, envParserPath, envParserImportPattern, loggerPath, loggerImportPattern);
|
|
41
|
+
routes.push({
|
|
42
|
+
path: "*",
|
|
43
|
+
method: "ALL",
|
|
44
|
+
handler: relative(process.cwd(), serverFile)
|
|
45
|
+
});
|
|
46
|
+
logger.log(`Generated server app with ${allEndpoints.length} endpoints`);
|
|
47
|
+
} else for (const { file, exportName, routeInfo } of allEndpoints) {
|
|
48
|
+
const handlerFile = await generateHandlerFile(outputDir, file, exportName, options.provider, routeInfo, envParserPath, envParserImportPattern);
|
|
49
|
+
routes.push({
|
|
50
|
+
...routeInfo,
|
|
51
|
+
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler")
|
|
52
|
+
});
|
|
53
|
+
logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
|
|
54
|
+
}
|
|
55
|
+
const manifest = { routes };
|
|
56
|
+
const manifestPath = join(outputDir, "routes.json");
|
|
57
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
58
|
+
logger.log(`Generated ${routes.length} handlers in ${relative(process.cwd(), outputDir)}`);
|
|
59
|
+
logger.log(`Routes manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
60
|
+
}
|
|
61
|
+
async function generateServerFile(outputDir, endpoints, envParserPath, envParserImportPattern, loggerPath, loggerImportPattern) {
|
|
62
|
+
const serverFileName = "app.ts";
|
|
63
|
+
const serverPath = join(outputDir, serverFileName);
|
|
64
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const { file, exportName } of endpoints) {
|
|
66
|
+
const relativePath = relative(dirname(serverPath), file);
|
|
67
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
68
|
+
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
69
|
+
importsByFile.get(importPath).push(exportName);
|
|
70
|
+
}
|
|
71
|
+
const relativeEnvParserPath = relative(dirname(serverPath), envParserPath);
|
|
72
|
+
const relativeLoggerPath = relative(dirname(serverPath), loggerPath);
|
|
73
|
+
const imports = Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n");
|
|
74
|
+
const allExportNames = endpoints.map(({ exportName }) => exportName);
|
|
75
|
+
const content = `import { HonoEndpoint } from '@geekmidas/api/hono';
|
|
76
|
+
import { HermodServiceDiscovery } from '@geekmidas/api/services';
|
|
77
|
+
import { Hono } from 'hono';
|
|
78
|
+
import ${envParserImportPattern} from '${relativeEnvParserPath}';
|
|
79
|
+
import ${loggerImportPattern} from '${relativeLoggerPath}';
|
|
80
|
+
${imports}
|
|
81
|
+
|
|
82
|
+
export function createApp(app?: Hono): Hono {
|
|
83
|
+
const honoApp = app || new Hono();
|
|
84
|
+
|
|
85
|
+
const endpoints = [
|
|
86
|
+
${allExportNames.join(",\n ")}
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const serviceDiscovery = HermodServiceDiscovery.getInstance(
|
|
90
|
+
logger,
|
|
91
|
+
envParser
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
HonoEndpoint.addRoutes(endpoints, serviceDiscovery, honoApp);
|
|
95
|
+
|
|
96
|
+
return honoApp;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Default export for convenience
|
|
100
|
+
export default createApp;
|
|
101
|
+
`;
|
|
102
|
+
await writeFile(serverPath, content);
|
|
103
|
+
return serverPath;
|
|
104
|
+
}
|
|
105
|
+
async function generateHandlerFile(outputDir, sourceFile, exportName, provider, routeInfo, envParserPath, envParserImportPattern) {
|
|
106
|
+
const handlerFileName = `${exportName}.ts`;
|
|
107
|
+
const handlerPath = join(outputDir, handlerFileName);
|
|
108
|
+
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
109
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
110
|
+
const relativeEnvParserPath = relative(dirname(handlerPath), envParserPath);
|
|
111
|
+
let content;
|
|
112
|
+
switch (provider) {
|
|
113
|
+
case "aws-apigatewayv1":
|
|
114
|
+
content = generateAWSApiGatewayV1Handler(importPath, exportName, relativeEnvParserPath, envParserImportPattern);
|
|
115
|
+
break;
|
|
116
|
+
case "aws-apigatewayv2":
|
|
117
|
+
content = generateAWSApiGatewayV2Handler(importPath, exportName, relativeEnvParserPath, envParserImportPattern);
|
|
118
|
+
break;
|
|
119
|
+
case "server":
|
|
120
|
+
content = generateServerHandler(importPath, exportName);
|
|
121
|
+
break;
|
|
122
|
+
default: throw new Error(`Unsupported provider: ${provider}`);
|
|
123
|
+
}
|
|
124
|
+
await writeFile(handlerPath, content);
|
|
125
|
+
return handlerPath;
|
|
126
|
+
}
|
|
127
|
+
function generateAWSApiGatewayV1Handler(importPath, exportName, envParserPath, envParserImportPattern) {
|
|
128
|
+
return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
129
|
+
import { ${exportName} } from '${importPath}';
|
|
130
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
131
|
+
|
|
132
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, ${exportName});
|
|
133
|
+
|
|
134
|
+
export const handler = adapter.handler;
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
function generateAWSApiGatewayV2Handler(importPath, exportName, envParserPath, envParserImportPattern) {
|
|
138
|
+
return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
139
|
+
import { ${exportName} } from '${importPath}';
|
|
140
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
141
|
+
|
|
142
|
+
const adapter = new AmazonApiGatewayV2Endpoint(envParser, ${exportName});
|
|
143
|
+
|
|
144
|
+
export const handler = adapter.handler;
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
function generateServerHandler(importPath, exportName) {
|
|
148
|
+
return `import { ${exportName} } from '${importPath}';
|
|
149
|
+
|
|
150
|
+
// Server handler - implement based on your server framework
|
|
151
|
+
export const handler = ${exportName};
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { buildCommand };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const require_config = require('./config-B-D8cs7V.cjs');
|
|
3
|
+
const require_loadEndpoints = require('./loadEndpoints-CYFwuPZr.cjs');
|
|
4
|
+
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
5
|
+
const path = require_chunk.__toESM(require("path"));
|
|
6
|
+
|
|
7
|
+
//#region src/build.ts
|
|
8
|
+
const logger = console;
|
|
9
|
+
async function buildCommand(options) {
|
|
10
|
+
logger.log(`Building with provider: ${options.provider}`);
|
|
11
|
+
const config = await require_config.loadConfig();
|
|
12
|
+
logger.log(`Loading routes from: ${config.routes}`);
|
|
13
|
+
logger.log(`Using envParser: ${config.envParser}`);
|
|
14
|
+
const [envParserPath, envParserName] = config.envParser.split("#");
|
|
15
|
+
const envParserImportPattern = !envParserName ? "envParser" : envParserName === "envParser" ? "{ envParser }" : `{ ${envParserName} as envParser }`;
|
|
16
|
+
const [loggerPath, loggerName] = config.logger.split("#");
|
|
17
|
+
const loggerImportPattern = !loggerName ? "logger" : loggerName === "logger" ? "{ logger }" : `{ ${loggerName} as logger }`;
|
|
18
|
+
const routes = [];
|
|
19
|
+
const outputDir = (0, path.join)(process.cwd(), ".gkm", options.provider);
|
|
20
|
+
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
21
|
+
const loadedEndpoints = await require_loadEndpoints.loadEndpoints(config.routes);
|
|
22
|
+
if (loadedEndpoints.length === 0) {
|
|
23
|
+
logger.log("No endpoints found to process");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const allEndpoints = loadedEndpoints.map(({ name, endpoint, file }) => {
|
|
27
|
+
const routeInfo = {
|
|
28
|
+
path: endpoint.route,
|
|
29
|
+
method: endpoint.method,
|
|
30
|
+
handler: ""
|
|
31
|
+
};
|
|
32
|
+
logger.log(`Found endpoint: ${name} - ${routeInfo.method} ${routeInfo.path}`);
|
|
33
|
+
return {
|
|
34
|
+
file: (0, path.relative)(process.cwd(), file),
|
|
35
|
+
exportName: name,
|
|
36
|
+
endpoint,
|
|
37
|
+
routeInfo
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
if (options.provider === "server") {
|
|
41
|
+
const serverFile = await generateServerFile(outputDir, allEndpoints, envParserPath, envParserImportPattern, loggerPath, loggerImportPattern);
|
|
42
|
+
routes.push({
|
|
43
|
+
path: "*",
|
|
44
|
+
method: "ALL",
|
|
45
|
+
handler: (0, path.relative)(process.cwd(), serverFile)
|
|
46
|
+
});
|
|
47
|
+
logger.log(`Generated server app with ${allEndpoints.length} endpoints`);
|
|
48
|
+
} else for (const { file, exportName, routeInfo } of allEndpoints) {
|
|
49
|
+
const handlerFile = await generateHandlerFile(outputDir, file, exportName, options.provider, routeInfo, envParserPath, envParserImportPattern);
|
|
50
|
+
routes.push({
|
|
51
|
+
...routeInfo,
|
|
52
|
+
handler: (0, path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler")
|
|
53
|
+
});
|
|
54
|
+
logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
|
|
55
|
+
}
|
|
56
|
+
const manifest = { routes };
|
|
57
|
+
const manifestPath = (0, path.join)(outputDir, "routes.json");
|
|
58
|
+
await (0, node_fs_promises.writeFile)(manifestPath, JSON.stringify(manifest, null, 2));
|
|
59
|
+
logger.log(`Generated ${routes.length} handlers in ${(0, path.relative)(process.cwd(), outputDir)}`);
|
|
60
|
+
logger.log(`Routes manifest: ${(0, path.relative)(process.cwd(), manifestPath)}`);
|
|
61
|
+
}
|
|
62
|
+
async function generateServerFile(outputDir, endpoints, envParserPath, envParserImportPattern, loggerPath, loggerImportPattern) {
|
|
63
|
+
const serverFileName = "app.ts";
|
|
64
|
+
const serverPath = (0, path.join)(outputDir, serverFileName);
|
|
65
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const { file, exportName } of endpoints) {
|
|
67
|
+
const relativePath = (0, path.relative)((0, path.dirname)(serverPath), file);
|
|
68
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
69
|
+
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
70
|
+
importsByFile.get(importPath).push(exportName);
|
|
71
|
+
}
|
|
72
|
+
const relativeEnvParserPath = (0, path.relative)((0, path.dirname)(serverPath), envParserPath);
|
|
73
|
+
const relativeLoggerPath = (0, path.relative)((0, path.dirname)(serverPath), loggerPath);
|
|
74
|
+
const imports = Array.from(importsByFile.entries()).map(([importPath, exports$1]) => `import { ${exports$1.join(", ")} } from '${importPath}';`).join("\n");
|
|
75
|
+
const allExportNames = endpoints.map(({ exportName }) => exportName);
|
|
76
|
+
const content = `import { HonoEndpoint } from '@geekmidas/api/hono';
|
|
77
|
+
import { HermodServiceDiscovery } from '@geekmidas/api/services';
|
|
78
|
+
import { Hono } from 'hono';
|
|
79
|
+
import ${envParserImportPattern} from '${relativeEnvParserPath}';
|
|
80
|
+
import ${loggerImportPattern} from '${relativeLoggerPath}';
|
|
81
|
+
${imports}
|
|
82
|
+
|
|
83
|
+
export function createApp(app?: Hono): Hono {
|
|
84
|
+
const honoApp = app || new Hono();
|
|
85
|
+
|
|
86
|
+
const endpoints = [
|
|
87
|
+
${allExportNames.join(",\n ")}
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const serviceDiscovery = HermodServiceDiscovery.getInstance(
|
|
91
|
+
logger,
|
|
92
|
+
envParser
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
HonoEndpoint.addRoutes(endpoints, serviceDiscovery, honoApp);
|
|
96
|
+
|
|
97
|
+
return honoApp;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Default export for convenience
|
|
101
|
+
export default createApp;
|
|
102
|
+
`;
|
|
103
|
+
await (0, node_fs_promises.writeFile)(serverPath, content);
|
|
104
|
+
return serverPath;
|
|
105
|
+
}
|
|
106
|
+
async function generateHandlerFile(outputDir, sourceFile, exportName, provider, routeInfo, envParserPath, envParserImportPattern) {
|
|
107
|
+
const handlerFileName = `${exportName}.ts`;
|
|
108
|
+
const handlerPath = (0, path.join)(outputDir, handlerFileName);
|
|
109
|
+
const relativePath = (0, path.relative)((0, path.dirname)(handlerPath), sourceFile);
|
|
110
|
+
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
111
|
+
const relativeEnvParserPath = (0, path.relative)((0, path.dirname)(handlerPath), envParserPath);
|
|
112
|
+
let content;
|
|
113
|
+
switch (provider) {
|
|
114
|
+
case "aws-apigatewayv1":
|
|
115
|
+
content = generateAWSApiGatewayV1Handler(importPath, exportName, relativeEnvParserPath, envParserImportPattern);
|
|
116
|
+
break;
|
|
117
|
+
case "aws-apigatewayv2":
|
|
118
|
+
content = generateAWSApiGatewayV2Handler(importPath, exportName, relativeEnvParserPath, envParserImportPattern);
|
|
119
|
+
break;
|
|
120
|
+
case "server":
|
|
121
|
+
content = generateServerHandler(importPath, exportName);
|
|
122
|
+
break;
|
|
123
|
+
default: throw new Error(`Unsupported provider: ${provider}`);
|
|
124
|
+
}
|
|
125
|
+
await (0, node_fs_promises.writeFile)(handlerPath, content);
|
|
126
|
+
return handlerPath;
|
|
127
|
+
}
|
|
128
|
+
function generateAWSApiGatewayV1Handler(importPath, exportName, envParserPath, envParserImportPattern) {
|
|
129
|
+
return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
130
|
+
import { ${exportName} } from '${importPath}';
|
|
131
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
132
|
+
|
|
133
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, ${exportName});
|
|
134
|
+
|
|
135
|
+
export const handler = adapter.handler;
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
function generateAWSApiGatewayV2Handler(importPath, exportName, envParserPath, envParserImportPattern) {
|
|
139
|
+
return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
140
|
+
import { ${exportName} } from '${importPath}';
|
|
141
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
142
|
+
|
|
143
|
+
const adapter = new AmazonApiGatewayV2Endpoint(envParser, ${exportName});
|
|
144
|
+
|
|
145
|
+
export const handler = adapter.handler;
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
function generateServerHandler(importPath, exportName) {
|
|
149
|
+
return `import { ${exportName} } from '${importPath}';
|
|
150
|
+
|
|
151
|
+
// Server handler - implement based on your server framework
|
|
152
|
+
export const handler = ${exportName};
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
Object.defineProperty(exports, 'buildCommand', {
|
|
158
|
+
enumerable: true,
|
|
159
|
+
get: function () {
|
|
160
|
+
return buildCommand;
|
|
161
|
+
}
|
|
162
|
+
});
|
package/dist/build.cjs
ADDED
package/dist/build.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(exports, '__toESM', {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function () {
|
|
28
|
+
return __toESM;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { buildCommand } from "./build-BgAllbWO.mjs";
|
|
2
|
+
import { openapiCommand } from "./openapi-DLix_iqn.mjs";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
|
|
5
|
+
//#region src/cli.ts
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program.name("gkm").description("GeekMidas backend framework CLI").version("0.0.2");
|
|
8
|
+
program.command("build").description("Build API handlers from endpoints").option("--provider <provider>", "Target provider for generated handlers", "aws-apigatewayv1").action(async (options) => {
|
|
9
|
+
try {
|
|
10
|
+
await buildCommand(options);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error("Build failed:", error.message);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
program.command("cron").description("Manage cron jobs").action(() => {
|
|
17
|
+
process.stdout.write("Cron management - coming soon\n");
|
|
18
|
+
});
|
|
19
|
+
program.command("function").description("Manage serverless functions").action(() => {
|
|
20
|
+
process.stdout.write("Serverless function management - coming soon\n");
|
|
21
|
+
});
|
|
22
|
+
program.command("api").description("Manage REST API endpoints").action(() => {
|
|
23
|
+
process.stdout.write("REST API management - coming soon\n");
|
|
24
|
+
});
|
|
25
|
+
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) => {
|
|
26
|
+
try {
|
|
27
|
+
await openapiCommand(options);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("OpenAPI generation failed:", error.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
program.parse();
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const require_build = require('./build-DN7u1ccT.cjs');
|
|
3
|
+
const require_openapi = require('./openapi-BKjcm03Q.cjs');
|
|
4
|
+
const commander = require_chunk.__toESM(require("commander"));
|
|
5
|
+
|
|
6
|
+
//#region src/cli.ts
|
|
7
|
+
const program = new commander.Command();
|
|
8
|
+
program.name("gkm").description("GeekMidas backend framework CLI").version("0.0.2");
|
|
9
|
+
program.command("build").description("Build API handlers from endpoints").option("--provider <provider>", "Target provider for generated handlers", "aws-apigatewayv1").action(async (options) => {
|
|
10
|
+
try {
|
|
11
|
+
await require_build.buildCommand(options);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error("Build failed:", error.message);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
program.command("cron").description("Manage cron jobs").action(() => {
|
|
18
|
+
process.stdout.write("Cron management - coming soon\n");
|
|
19
|
+
});
|
|
20
|
+
program.command("function").description("Manage serverless functions").action(() => {
|
|
21
|
+
process.stdout.write("Serverless function management - coming soon\n");
|
|
22
|
+
});
|
|
23
|
+
program.command("api").description("Manage REST API endpoints").action(() => {
|
|
24
|
+
process.stdout.write("REST API management - coming soon\n");
|
|
25
|
+
});
|
|
26
|
+
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) => {
|
|
27
|
+
try {
|
|
28
|
+
await require_openapi.openapiCommand(options);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("OpenAPI generation failed:", error.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
program.parse();
|
|
35
|
+
|
|
36
|
+
//#endregion
|
package/dist/cli.cjs
ADDED
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const path = require_chunk.__toESM(require("path"));
|
|
3
|
+
const fs = require_chunk.__toESM(require("fs"));
|
|
4
|
+
|
|
5
|
+
//#region src/config.ts
|
|
6
|
+
async function loadConfig() {
|
|
7
|
+
const configPath = (0, path.join)(process.cwd(), "gkm.config.ts");
|
|
8
|
+
if (!(0, fs.existsSync)(configPath)) throw new Error("gkm.config.ts not found. Please create a configuration file.");
|
|
9
|
+
try {
|
|
10
|
+
const config = await import(configPath);
|
|
11
|
+
return config.default || config;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Failed to load gkm.config.ts: ${error.message}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
Object.defineProperty(exports, 'loadConfig', {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return loadConfig;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
|
|
4
|
+
//#region src/config.ts
|
|
5
|
+
async function loadConfig() {
|
|
6
|
+
const configPath = join(process.cwd(), "gkm.config.ts");
|
|
7
|
+
if (!existsSync(configPath)) throw new Error("gkm.config.ts not found. Please create a configuration file.");
|
|
8
|
+
try {
|
|
9
|
+
const config = await import(configPath);
|
|
10
|
+
return config.default || config;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw new Error(`Failed to load gkm.config.ts: ${error.message}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { loadConfig };
|
package/dist/config.cjs
ADDED
package/dist/config.mjs
ADDED
package/dist/index.cjs
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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)) endpoints.push({
|
|
16
|
+
name: exportName,
|
|
17
|
+
endpoint: exportValue,
|
|
18
|
+
file
|
|
19
|
+
});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.warn(`Failed to load ${f}:`, error.message);
|
|
22
|
+
}
|
|
23
|
+
return endpoints;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { loadEndpoints };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const __geekmidas_api_server = require_chunk.__toESM(require("@geekmidas/api/server"));
|
|
3
|
+
const fast_glob = require_chunk.__toESM(require("fast-glob"));
|
|
4
|
+
|
|
5
|
+
//#region src/loadEndpoints.ts
|
|
6
|
+
async function loadEndpoints(routes) {
|
|
7
|
+
const logger = console;
|
|
8
|
+
const files = await fast_glob.default.stream(routes, {
|
|
9
|
+
cwd: process.cwd(),
|
|
10
|
+
absolute: true
|
|
11
|
+
});
|
|
12
|
+
const endpoints = [];
|
|
13
|
+
for await (const f of files) try {
|
|
14
|
+
const file = f.toString();
|
|
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
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
logger.warn(`Failed to load ${f}:`, error.message);
|
|
23
|
+
}
|
|
24
|
+
return endpoints;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
Object.defineProperty(exports, 'loadEndpoints', {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
get: function () {
|
|
31
|
+
return loadEndpoints;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const require_config = require('./config-B-D8cs7V.cjs');
|
|
3
|
+
const require_loadEndpoints = require('./loadEndpoints-CYFwuPZr.cjs');
|
|
4
|
+
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
5
|
+
const __geekmidas_api_server = require_chunk.__toESM(require("@geekmidas/api/server"));
|
|
6
|
+
const node_path = require_chunk.__toESM(require("node:path"));
|
|
7
|
+
|
|
8
|
+
//#region src/openapi.ts
|
|
9
|
+
async function openapiCommand(options = {}) {
|
|
10
|
+
const logger = console;
|
|
11
|
+
try {
|
|
12
|
+
const config = await require_config.loadConfig();
|
|
13
|
+
const loadedEndpoints = await require_loadEndpoints.loadEndpoints(config.routes);
|
|
14
|
+
if (loadedEndpoints.length === 0) {
|
|
15
|
+
logger.log("No valid endpoints found");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const endpoints = loadedEndpoints.map(({ endpoint }) => endpoint);
|
|
19
|
+
const spec = await __geekmidas_api_server.Endpoint.buildOpenApiSchema(endpoints, {
|
|
20
|
+
title: "API Documentation",
|
|
21
|
+
version: "1.0.0",
|
|
22
|
+
description: "Auto-generated API documentation from endpoints"
|
|
23
|
+
});
|
|
24
|
+
const outputPath = options.output || (0, node_path.join)(process.cwd(), "openapi.json");
|
|
25
|
+
await (0, node_fs_promises.mkdir)((0, node_path.join)(outputPath, ".."), { recursive: true });
|
|
26
|
+
await (0, node_fs_promises.writeFile)(outputPath, JSON.stringify(spec, null, 2));
|
|
27
|
+
logger.log(`OpenAPI spec generated: ${outputPath}`);
|
|
28
|
+
logger.log(`Found ${endpoints.length} endpoints`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`OpenAPI generation failed: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
Object.defineProperty(exports, 'openapiCommand', {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
get: function () {
|
|
38
|
+
return openapiCommand;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { loadConfig } from "./config-Bzzc89wR.mjs";
|
|
2
|
+
import { loadEndpoints } from "./loadEndpoints-BL8q2rTO.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 };
|
package/dist/openapi.cjs
ADDED
package/dist/openapi.mjs
ADDED
package/dist/types.cjs
ADDED
|
File without changes
|
package/dist/types.mjs
ADDED
|
File without changes
|
package/examples/env.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
2
|
+
|
|
3
|
+
export const envParser = new EnvironmentParser(process.env)
|
|
4
|
+
.create((get) => ({
|
|
5
|
+
// Database configuration
|
|
6
|
+
database: {
|
|
7
|
+
url: get('DATABASE_URL').string().url(),
|
|
8
|
+
maxConnections: get('DB_MAX_CONNECTIONS')
|
|
9
|
+
.string()
|
|
10
|
+
.transform(Number)
|
|
11
|
+
.default('10'),
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
// API configuration
|
|
15
|
+
api: {
|
|
16
|
+
port: get('PORT').string().transform(Number).default('3000'),
|
|
17
|
+
cors: {
|
|
18
|
+
origin: get('CORS_ORIGIN').string().default('*'),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// AWS configuration (for Lambda deployments)
|
|
23
|
+
aws: {
|
|
24
|
+
region: get('AWS_REGION').string().default('us-east-1'),
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Application settings
|
|
28
|
+
app: {
|
|
29
|
+
environment: get('NODE_ENV').string().default('development'),
|
|
30
|
+
logLevel: get('LOG_LEVEL').string().default('info'),
|
|
31
|
+
},
|
|
32
|
+
}))
|
|
33
|
+
.parse();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GkmConfig } from '@geekmidas/cli';
|
|
2
|
+
|
|
3
|
+
const config: GkmConfig = {
|
|
4
|
+
// Glob pattern to find endpoint files
|
|
5
|
+
routes: 'src/routes/**/*.ts',
|
|
6
|
+
|
|
7
|
+
// Environment parser configuration
|
|
8
|
+
// Format: path#exportName (if no #exportName, treats as default import)
|
|
9
|
+
envParser: './src/env.ts#envParser',
|
|
10
|
+
|
|
11
|
+
// Logger configuration
|
|
12
|
+
// Format: path#exportName (if no #exportName, treats as default import)
|
|
13
|
+
logger: './src/logger.ts#logger',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default config;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConsoleLogger } from '@geekmidas/api/logger';
|
|
2
|
+
|
|
3
|
+
// Create a console logger instance
|
|
4
|
+
export const logger = new ConsoleLogger({
|
|
5
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
6
|
+
pretty: process.env.NODE_ENV !== 'production',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// You can also export a default logger
|
|
10
|
+
export default logger;
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geekmidas/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gkm": "./dist/index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./src/index.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"registry": "https://registry.npmjs.org/",
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "~14.0.0",
|
|
22
|
+
"lodash.get": "~4.4.2",
|
|
23
|
+
"lodash.set": "~4.3.2",
|
|
24
|
+
"zod": "~3.25.67",
|
|
25
|
+
"fast-glob": "~3.3.3",
|
|
26
|
+
"@geekmidas/api": "0.0.3"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/lodash.get": "~4.4.9",
|
|
30
|
+
"@types/lodash.set": "~4.3.9"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, relative } from 'path';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
import { loadEndpoints } from './loadEndpoints.js';
|
|
5
|
+
import type {
|
|
6
|
+
BuildOptions,
|
|
7
|
+
Provider,
|
|
8
|
+
RouteInfo,
|
|
9
|
+
RoutesManifest,
|
|
10
|
+
} from './types.js';
|
|
11
|
+
|
|
12
|
+
const logger = console;
|
|
13
|
+
export async function buildCommand(options: BuildOptions): Promise<void> {
|
|
14
|
+
logger.log(`Building with provider: ${options.provider}`);
|
|
15
|
+
|
|
16
|
+
const config = await loadConfig();
|
|
17
|
+
logger.log(`Loading routes from: ${config.routes}`);
|
|
18
|
+
logger.log(`Using envParser: ${config.envParser}`);
|
|
19
|
+
|
|
20
|
+
// Parse envParser configuration
|
|
21
|
+
const [envParserPath, envParserName] = config.envParser.split('#');
|
|
22
|
+
const envParserImportPattern = !envParserName
|
|
23
|
+
? 'envParser'
|
|
24
|
+
: envParserName === 'envParser'
|
|
25
|
+
? '{ envParser }'
|
|
26
|
+
: `{ ${envParserName} as envParser }`;
|
|
27
|
+
|
|
28
|
+
// Parse logger configuration
|
|
29
|
+
const [loggerPath, loggerName] = config.logger.split('#');
|
|
30
|
+
const loggerImportPattern = !loggerName
|
|
31
|
+
? 'logger'
|
|
32
|
+
: loggerName === 'logger'
|
|
33
|
+
? '{ logger }'
|
|
34
|
+
: `{ ${loggerName} as logger }`;
|
|
35
|
+
|
|
36
|
+
const routes: RouteInfo[] = [];
|
|
37
|
+
const outputDir = join(process.cwd(), '.gkm', options.provider);
|
|
38
|
+
|
|
39
|
+
// Ensure output directory exists
|
|
40
|
+
await mkdir(outputDir, { recursive: true });
|
|
41
|
+
|
|
42
|
+
// Load all endpoints using the refactored function
|
|
43
|
+
const loadedEndpoints = await loadEndpoints(config.routes);
|
|
44
|
+
|
|
45
|
+
if (loadedEndpoints.length === 0) {
|
|
46
|
+
logger.log('No endpoints found to process');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const allEndpoints = loadedEndpoints.map(({ name, endpoint, file }) => {
|
|
51
|
+
const routeInfo: RouteInfo = {
|
|
52
|
+
path: endpoint.route,
|
|
53
|
+
method: endpoint.method,
|
|
54
|
+
handler: '', // Will be filled in later
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
logger.log(
|
|
58
|
+
`Found endpoint: ${name} - ${routeInfo.method} ${routeInfo.path}`,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
file: relative(process.cwd(), file),
|
|
63
|
+
exportName: name,
|
|
64
|
+
endpoint,
|
|
65
|
+
routeInfo,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Generate handlers based on provider
|
|
70
|
+
if (options.provider === 'server') {
|
|
71
|
+
// Generate single server file with all endpoints
|
|
72
|
+
const serverFile = await generateServerFile(
|
|
73
|
+
outputDir,
|
|
74
|
+
allEndpoints,
|
|
75
|
+
envParserPath,
|
|
76
|
+
envParserImportPattern,
|
|
77
|
+
loggerPath,
|
|
78
|
+
loggerImportPattern,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
routes.push({
|
|
82
|
+
path: '*',
|
|
83
|
+
method: 'ALL',
|
|
84
|
+
handler: relative(process.cwd(), serverFile),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
logger.log(`Generated server app with ${allEndpoints.length} endpoints`);
|
|
88
|
+
} else {
|
|
89
|
+
// Generate individual handler files for AWS providers
|
|
90
|
+
for (const { file, exportName, routeInfo } of allEndpoints) {
|
|
91
|
+
const handlerFile = await generateHandlerFile(
|
|
92
|
+
outputDir,
|
|
93
|
+
file,
|
|
94
|
+
exportName,
|
|
95
|
+
options.provider,
|
|
96
|
+
routeInfo,
|
|
97
|
+
envParserPath,
|
|
98
|
+
envParserImportPattern,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
routes.push({
|
|
102
|
+
...routeInfo,
|
|
103
|
+
handler: relative(process.cwd(), handlerFile).replace(
|
|
104
|
+
/\.ts$/,
|
|
105
|
+
'.handler',
|
|
106
|
+
),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Generate routes.json
|
|
114
|
+
const manifest: RoutesManifest = { routes };
|
|
115
|
+
const manifestPath = join(outputDir, 'routes.json');
|
|
116
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
117
|
+
|
|
118
|
+
logger.log(
|
|
119
|
+
`Generated ${routes.length} handlers in ${relative(process.cwd(), outputDir)}`,
|
|
120
|
+
);
|
|
121
|
+
logger.log(`Routes manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function generateServerFile(
|
|
125
|
+
outputDir: string,
|
|
126
|
+
endpoints: Array<{
|
|
127
|
+
file: string;
|
|
128
|
+
exportName: string;
|
|
129
|
+
endpoint: any;
|
|
130
|
+
routeInfo: RouteInfo;
|
|
131
|
+
}>,
|
|
132
|
+
envParserPath: string,
|
|
133
|
+
envParserImportPattern: string,
|
|
134
|
+
loggerPath: string,
|
|
135
|
+
loggerImportPattern: string,
|
|
136
|
+
): Promise<string> {
|
|
137
|
+
const serverFileName = 'app.ts';
|
|
138
|
+
const serverPath = join(outputDir, serverFileName);
|
|
139
|
+
|
|
140
|
+
// Group imports by file
|
|
141
|
+
const importsByFile = new Map<string, string[]>();
|
|
142
|
+
|
|
143
|
+
for (const { file, exportName } of endpoints) {
|
|
144
|
+
const relativePath = relative(dirname(serverPath), file);
|
|
145
|
+
const importPath = relativePath.replace(/\.ts$/, '.js');
|
|
146
|
+
|
|
147
|
+
if (!importsByFile.has(importPath)) {
|
|
148
|
+
importsByFile.set(importPath, []);
|
|
149
|
+
}
|
|
150
|
+
importsByFile.get(importPath)!.push(exportName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const relativeEnvParserPath = relative(dirname(serverPath), envParserPath);
|
|
154
|
+
const relativeLoggerPath = relative(dirname(serverPath), loggerPath);
|
|
155
|
+
|
|
156
|
+
// Generate import statements
|
|
157
|
+
const imports = Array.from(importsByFile.entries())
|
|
158
|
+
.map(
|
|
159
|
+
([importPath, exports]) =>
|
|
160
|
+
`import { ${exports.join(', ')} } from '${importPath}';`,
|
|
161
|
+
)
|
|
162
|
+
.join('\n');
|
|
163
|
+
|
|
164
|
+
const allExportNames = endpoints.map(({ exportName }) => exportName);
|
|
165
|
+
|
|
166
|
+
const content = `import { HonoEndpoint } from '@geekmidas/api/hono';
|
|
167
|
+
import { HermodServiceDiscovery } from '@geekmidas/api/services';
|
|
168
|
+
import { Hono } from 'hono';
|
|
169
|
+
import ${envParserImportPattern} from '${relativeEnvParserPath}';
|
|
170
|
+
import ${loggerImportPattern} from '${relativeLoggerPath}';
|
|
171
|
+
${imports}
|
|
172
|
+
|
|
173
|
+
export function createApp(app?: Hono): Hono {
|
|
174
|
+
const honoApp = app || new Hono();
|
|
175
|
+
|
|
176
|
+
const endpoints = [
|
|
177
|
+
${allExportNames.join(',\n ')}
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const serviceDiscovery = HermodServiceDiscovery.getInstance(
|
|
181
|
+
logger,
|
|
182
|
+
envParser
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
HonoEndpoint.addRoutes(endpoints, serviceDiscovery, honoApp);
|
|
186
|
+
|
|
187
|
+
return honoApp;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Default export for convenience
|
|
191
|
+
export default createApp;
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
await writeFile(serverPath, content);
|
|
195
|
+
return serverPath;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function generateHandlerFile(
|
|
199
|
+
outputDir: string,
|
|
200
|
+
sourceFile: string,
|
|
201
|
+
exportName: string,
|
|
202
|
+
provider: Provider,
|
|
203
|
+
routeInfo: RouteInfo,
|
|
204
|
+
envParserPath: string,
|
|
205
|
+
envParserImportPattern: string,
|
|
206
|
+
): Promise<string> {
|
|
207
|
+
const handlerFileName = `${exportName}.ts`;
|
|
208
|
+
const handlerPath = join(outputDir, handlerFileName);
|
|
209
|
+
|
|
210
|
+
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
211
|
+
const importPath = relativePath.replace(/\.ts$/, '.js');
|
|
212
|
+
|
|
213
|
+
const relativeEnvParserPath = relative(dirname(handlerPath), envParserPath);
|
|
214
|
+
|
|
215
|
+
let content: string;
|
|
216
|
+
|
|
217
|
+
switch (provider) {
|
|
218
|
+
case 'aws-apigatewayv1':
|
|
219
|
+
content = generateAWSApiGatewayV1Handler(
|
|
220
|
+
importPath,
|
|
221
|
+
exportName,
|
|
222
|
+
relativeEnvParserPath,
|
|
223
|
+
envParserImportPattern,
|
|
224
|
+
);
|
|
225
|
+
break;
|
|
226
|
+
case 'aws-apigatewayv2':
|
|
227
|
+
content = generateAWSApiGatewayV2Handler(
|
|
228
|
+
importPath,
|
|
229
|
+
exportName,
|
|
230
|
+
relativeEnvParserPath,
|
|
231
|
+
envParserImportPattern,
|
|
232
|
+
);
|
|
233
|
+
break;
|
|
234
|
+
case 'server':
|
|
235
|
+
content = generateServerHandler(importPath, exportName);
|
|
236
|
+
break;
|
|
237
|
+
default:
|
|
238
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
await writeFile(handlerPath, content);
|
|
242
|
+
return handlerPath;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function generateAWSApiGatewayV1Handler(
|
|
246
|
+
importPath: string,
|
|
247
|
+
exportName: string,
|
|
248
|
+
envParserPath: string,
|
|
249
|
+
envParserImportPattern: string,
|
|
250
|
+
): string {
|
|
251
|
+
return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
252
|
+
import { ${exportName} } from '${importPath}';
|
|
253
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
254
|
+
|
|
255
|
+
const adapter = new AmazonApiGatewayV1Endpoint(envParser, ${exportName});
|
|
256
|
+
|
|
257
|
+
export const handler = adapter.handler;
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function generateAWSApiGatewayV2Handler(
|
|
262
|
+
importPath: string,
|
|
263
|
+
exportName: string,
|
|
264
|
+
envParserPath: string,
|
|
265
|
+
envParserImportPattern: string,
|
|
266
|
+
): string {
|
|
267
|
+
return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/api/aws-apigateway';
|
|
268
|
+
import { ${exportName} } from '${importPath}';
|
|
269
|
+
import ${envParserImportPattern} from '${envParserPath}';
|
|
270
|
+
|
|
271
|
+
const adapter = new AmazonApiGatewayV2Endpoint(envParser, ${exportName});
|
|
272
|
+
|
|
273
|
+
export const handler = adapter.handler;
|
|
274
|
+
`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function generateServerHandler(importPath: string, exportName: string): string {
|
|
278
|
+
return `import { ${exportName} } from '${importPath}';
|
|
279
|
+
|
|
280
|
+
// Server handler - implement based on your server framework
|
|
281
|
+
export const handler = ${exportName};
|
|
282
|
+
`;
|
|
283
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { buildCommand } from './build.js';
|
|
5
|
+
import { openapiCommand } from './openapi.js';
|
|
6
|
+
import type { Provider } from './types.js';
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('gkm')
|
|
12
|
+
.description('GeekMidas backend framework CLI')
|
|
13
|
+
.version('0.0.2');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('build')
|
|
17
|
+
.description('Build API handlers from endpoints')
|
|
18
|
+
.option(
|
|
19
|
+
'--provider <provider>',
|
|
20
|
+
'Target provider for generated handlers',
|
|
21
|
+
'aws-apigatewayv1',
|
|
22
|
+
)
|
|
23
|
+
.action(async (options: { provider: Provider }) => {
|
|
24
|
+
try {
|
|
25
|
+
await buildCommand(options);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Build failed:', (error as Error).message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('cron')
|
|
34
|
+
.description('Manage cron jobs')
|
|
35
|
+
.action(() => {
|
|
36
|
+
process.stdout.write('Cron management - coming soon\n');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('function')
|
|
41
|
+
.description('Manage serverless functions')
|
|
42
|
+
.action(() => {
|
|
43
|
+
process.stdout.write('Serverless function management - coming soon\n');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
program
|
|
47
|
+
.command('api')
|
|
48
|
+
.description('Manage REST API endpoints')
|
|
49
|
+
.action(() => {
|
|
50
|
+
process.stdout.write('REST API management - coming soon\n');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
program
|
|
54
|
+
.command('openapi')
|
|
55
|
+
.description('Generate OpenAPI 3.0 specification from endpoints')
|
|
56
|
+
.option(
|
|
57
|
+
'--output <path>',
|
|
58
|
+
'Output file path for the OpenAPI spec',
|
|
59
|
+
'openapi.json',
|
|
60
|
+
)
|
|
61
|
+
.action(async (options: { output?: string }) => {
|
|
62
|
+
try {
|
|
63
|
+
await openapiCommand(options);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('OpenAPI generation failed:', (error as Error).message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
program.parse();
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import type { GkmConfig } from './types.js';
|
|
4
|
+
|
|
5
|
+
export async function loadConfig(): Promise<GkmConfig> {
|
|
6
|
+
const configPath = join(process.cwd(), 'gkm.config.ts');
|
|
7
|
+
|
|
8
|
+
if (!existsSync(configPath)) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
'gkm.config.ts not found. Please create a configuration file.',
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const config = await import(configPath);
|
|
16
|
+
return config.default || config;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Failed to load gkm.config.ts: ${(error as Error).message}`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Endpoint } from '@geekmidas/api/server';
|
|
2
|
+
import fg from 'fast-glob';
|
|
3
|
+
|
|
4
|
+
export interface LoadedEndpoint {
|
|
5
|
+
name: string;
|
|
6
|
+
endpoint: Endpoint<any, any, any, any, any, any>;
|
|
7
|
+
file: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function loadEndpoints(routes: string): Promise<LoadedEndpoint[]> {
|
|
11
|
+
const logger = console;
|
|
12
|
+
|
|
13
|
+
// Find all endpoint files
|
|
14
|
+
const files = await fg.stream(routes, {
|
|
15
|
+
cwd: process.cwd(),
|
|
16
|
+
absolute: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Load endpoints
|
|
20
|
+
const endpoints: LoadedEndpoint[] = [];
|
|
21
|
+
|
|
22
|
+
for await (const f of files) {
|
|
23
|
+
try {
|
|
24
|
+
const file = f.toString();
|
|
25
|
+
const module = await import(file);
|
|
26
|
+
|
|
27
|
+
// Check all exports for endpoints
|
|
28
|
+
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
29
|
+
if (Endpoint.isEndpoint(exportValue as any)) {
|
|
30
|
+
endpoints.push({
|
|
31
|
+
name: exportName,
|
|
32
|
+
endpoint: exportValue as Endpoint<any, any, any, any, any, any>,
|
|
33
|
+
file,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.warn(`Failed to load ${f}:`, (error as Error).message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return endpoints;
|
|
43
|
+
}
|
package/src/openapi.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { Endpoint } from '@geekmidas/api/server';
|
|
6
|
+
import { loadConfig } from './config.js';
|
|
7
|
+
import { loadEndpoints } from './loadEndpoints.js';
|
|
8
|
+
|
|
9
|
+
interface OpenAPIOptions {
|
|
10
|
+
output?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function openapiCommand(
|
|
14
|
+
options: OpenAPIOptions = {},
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const logger = console;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Load config using existing function
|
|
20
|
+
const config = await loadConfig();
|
|
21
|
+
|
|
22
|
+
// Load all endpoints using the refactored function
|
|
23
|
+
const loadedEndpoints = await loadEndpoints(config.routes);
|
|
24
|
+
|
|
25
|
+
if (loadedEndpoints.length === 0) {
|
|
26
|
+
logger.log('No valid endpoints found');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Extract just the endpoint instances for OpenAPI generation
|
|
31
|
+
const endpoints = loadedEndpoints.map(({ endpoint }) => endpoint);
|
|
32
|
+
|
|
33
|
+
// Generate OpenAPI spec using built-in method
|
|
34
|
+
const spec = await Endpoint.buildOpenApiSchema(endpoints, {
|
|
35
|
+
title: 'API Documentation',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
description: 'Auto-generated API documentation from endpoints',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Write output
|
|
41
|
+
const outputPath = options.output || join(process.cwd(), 'openapi.json');
|
|
42
|
+
await mkdir(join(outputPath, '..'), { recursive: true });
|
|
43
|
+
await writeFile(outputPath, JSON.stringify(spec, null, 2));
|
|
44
|
+
|
|
45
|
+
logger.log(`OpenAPI spec generated: ${outputPath}`);
|
|
46
|
+
logger.log(`Found ${endpoints.length} endpoints`);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(`OpenAPI generation failed: ${(error as Error).message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Provider = 'server' | 'aws-apigatewayv1' | 'aws-apigatewayv2';
|
|
2
|
+
|
|
3
|
+
export interface GkmConfig {
|
|
4
|
+
routes: string;
|
|
5
|
+
envParser: string;
|
|
6
|
+
logger: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface BuildOptions {
|
|
10
|
+
provider: Provider;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RouteInfo {
|
|
14
|
+
path: string;
|
|
15
|
+
method: string;
|
|
16
|
+
handler: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RoutesManifest {
|
|
20
|
+
routes: RouteInfo[];
|
|
21
|
+
}
|
package/tsdown.config.ts
ADDED