kastrick 1.0.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 +38 -0
- package/examples/01-hello-world/api/hello.ts +19 -0
- package/examples/02-users-api/README.md +39 -0
- package/examples/02-users-api/api/users/[id].ts +80 -0
- package/examples/02-users-api/api/users/index.ts +50 -0
- package/examples/03-configuration/README.md +39 -0
- package/examples/03-configuration/kastrick.config.json +22 -0
- package/examples/03-configuration/tsconfig.json +20 -0
- package/examples/README.md +48 -0
- package/lib/cli/compile.d.ts +5 -0
- package/lib/cli/compile.d.ts.map +1 -0
- package/lib/cli/compile.js +25 -0
- package/lib/cli/compile.js.map +1 -0
- package/lib/cli/dev.d.ts +5 -0
- package/lib/cli/dev.d.ts.map +1 -0
- package/lib/cli/dev.js +79 -0
- package/lib/cli/dev.js.map +1 -0
- package/lib/cli/index.d.ts +3 -0
- package/lib/cli/index.d.ts.map +1 -0
- package/lib/cli/index.js +63 -0
- package/lib/cli/index.js.map +1 -0
- package/lib/cli/serve.d.ts +5 -0
- package/lib/cli/serve.d.ts.map +1 -0
- package/lib/cli/serve.js +106 -0
- package/lib/cli/serve.js.map +1 -0
- package/lib/core/compiler.d.ts +29 -0
- package/lib/core/compiler.d.ts.map +1 -0
- package/lib/core/compiler.js +153 -0
- package/lib/core/compiler.js.map +1 -0
- package/lib/core/router.d.ts +31 -0
- package/lib/core/router.d.ts.map +1 -0
- package/lib/core/router.js +106 -0
- package/lib/core/router.js.map +1 -0
- package/lib/core/scanner.d.ts +29 -0
- package/lib/core/scanner.d.ts.map +1 -0
- package/lib/core/scanner.js +88 -0
- package/lib/core/scanner.js.map +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +35 -0
- package/lib/index.js.map +1 -0
- package/lib/server/expressServer.d.ts +28 -0
- package/lib/server/expressServer.d.ts.map +1 -0
- package/lib/server/expressServer.js +96 -0
- package/lib/server/expressServer.js.map +1 -0
- package/package.json +34 -0
- package/src/cli/compile.ts +27 -0
- package/src/cli/dev.ts +49 -0
- package/src/cli/index.ts +64 -0
- package/src/cli/serve.ts +81 -0
- package/src/core/compiler.ts +147 -0
- package/src/core/router.ts +88 -0
- package/src/core/scanner.ts +72 -0
- package/src/index.ts +38 -0
- package/src/server/expressServer.ts +107 -0
- package/templates/tsconfig.json +20 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { ApiFile } from "../core/scanner";
|
|
3
|
+
/**
|
|
4
|
+
* Express Server - Dev server for local API testing
|
|
5
|
+
*/
|
|
6
|
+
export declare class ExpressServer {
|
|
7
|
+
private app;
|
|
8
|
+
private port;
|
|
9
|
+
private router;
|
|
10
|
+
private loadedModules;
|
|
11
|
+
constructor(port?: number);
|
|
12
|
+
private setupMiddleware;
|
|
13
|
+
/**
|
|
14
|
+
* Register routes from API files
|
|
15
|
+
*/
|
|
16
|
+
registerRoutes(files: ApiFile[]): void;
|
|
17
|
+
private createHandler;
|
|
18
|
+
private parseHandler;
|
|
19
|
+
/**
|
|
20
|
+
* Start the dev server
|
|
21
|
+
*/
|
|
22
|
+
start(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Get Express app instance
|
|
25
|
+
*/
|
|
26
|
+
getApp(): express.Application;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=expressServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expressServer.d.ts","sourceRoot":"","sources":["../../src/server/expressServer.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAG9B,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAA+B;gBAExC,IAAI,GAAE,MAAa;IAO/B,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI;IActC,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,YAAY;IAuBpB;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,WAAW;CAG9B"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ExpressServer = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const router_1 = require("../core/router");
|
|
9
|
+
/**
|
|
10
|
+
* Express Server - Dev server for local API testing
|
|
11
|
+
*/
|
|
12
|
+
class ExpressServer {
|
|
13
|
+
constructor(port = 3000) {
|
|
14
|
+
this.loadedModules = new Map();
|
|
15
|
+
this.app = (0, express_1.default)();
|
|
16
|
+
this.port = port;
|
|
17
|
+
this.router = new router_1.Router();
|
|
18
|
+
this.setupMiddleware();
|
|
19
|
+
}
|
|
20
|
+
setupMiddleware() {
|
|
21
|
+
this.app.use(express_1.default.json());
|
|
22
|
+
this.app.use(express_1.default.urlencoded({ extended: true }));
|
|
23
|
+
// Simple logging middleware
|
|
24
|
+
this.app.use((req, res, next) => {
|
|
25
|
+
console.log(`📡 ${req.method} ${req.path}`);
|
|
26
|
+
next();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Register routes from API files
|
|
31
|
+
*/
|
|
32
|
+
registerRoutes(files) {
|
|
33
|
+
const routes = this.router.generateRoutes(files);
|
|
34
|
+
for (const route of routes) {
|
|
35
|
+
const method = route.method.toLowerCase();
|
|
36
|
+
const filePath = route.file;
|
|
37
|
+
const content = files.find((f) => f.relativePath === filePath)?.content || "";
|
|
38
|
+
this.app[method](route.path, this.createHandler(filePath, route.method, content));
|
|
39
|
+
}
|
|
40
|
+
console.log(`📡 Registered ${routes.length} routes`);
|
|
41
|
+
}
|
|
42
|
+
createHandler(filePath, method, content) {
|
|
43
|
+
return async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
// Parse and execute handler
|
|
46
|
+
const fn = this.parseHandler(content, method);
|
|
47
|
+
if (!fn) {
|
|
48
|
+
return res.status(404).json({ error: `${method} handler not found` });
|
|
49
|
+
}
|
|
50
|
+
const result = await fn(req, { params: req.params });
|
|
51
|
+
res.status(result.status || 200).json(result.body !== undefined ? result.body : result);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error(`❌ Handler error in ${filePath}:`, err.message);
|
|
55
|
+
res.status(500).json({ error: err.message || "Internal server error" });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
parseHandler(content, method) {
|
|
60
|
+
try {
|
|
61
|
+
// Extract the function from the file content
|
|
62
|
+
const moduleExports = {};
|
|
63
|
+
// Create a minimal module context
|
|
64
|
+
const module = { exports: moduleExports };
|
|
65
|
+
const exportFn = (key, fn) => {
|
|
66
|
+
moduleExports[key] = fn;
|
|
67
|
+
};
|
|
68
|
+
// Execute the file content in a controlled context
|
|
69
|
+
const code = `(function(exports, require, module, __filename, __dirname) { ${content} return module.exports; })`;
|
|
70
|
+
const fn = eval(code);
|
|
71
|
+
const result = fn(moduleExports, require, module, "", "");
|
|
72
|
+
return result[method];
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error("Error parsing handler:", err);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Start the dev server
|
|
81
|
+
*/
|
|
82
|
+
start() {
|
|
83
|
+
this.app.listen(this.port, () => {
|
|
84
|
+
console.log(`\n🚀 Kastrick dev server running on http://localhost:${this.port}\n`);
|
|
85
|
+
console.log(`👀 Watching for changes in /api folder...\n`);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get Express app instance
|
|
90
|
+
*/
|
|
91
|
+
getApp() {
|
|
92
|
+
return this.app;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.ExpressServer = ExpressServer;
|
|
96
|
+
//# sourceMappingURL=expressServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expressServer.js","sourceRoot":"","sources":["../../src/server/expressServer.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kastrick",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "a Easy Tool for Kastrick Services and Devoloper Experience",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"kastrick": "lib/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"api",
|
|
18
|
+
"kastrick",
|
|
19
|
+
"framework",
|
|
20
|
+
"routing",
|
|
21
|
+
"backend"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "ISC",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"express": "^4.18.0",
|
|
27
|
+
"chokidar": "^3.5.0",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.0.0",
|
|
32
|
+
"@types/express": "^4.17.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Scanner } from "../core/scanner";
|
|
2
|
+
import { Compiler } from "../core/compiler";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compile command - builds the /output folder
|
|
6
|
+
*/
|
|
7
|
+
export async function compile(rootDir: string = process.cwd()): Promise<void> {
|
|
8
|
+
console.log("🔨 Compiling kastrick project...\n");
|
|
9
|
+
|
|
10
|
+
const scanner = new Scanner(rootDir);
|
|
11
|
+
const compiler = new Compiler(rootDir);
|
|
12
|
+
|
|
13
|
+
// Scan API folder
|
|
14
|
+
const files = scanner.scanApiFolder();
|
|
15
|
+
|
|
16
|
+
if (files.length === 0) {
|
|
17
|
+
console.log("⚠️ No API files found. Create files in /api folder first.");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`📁 Found ${files.length} API files`);
|
|
22
|
+
files.forEach((f) => console.log(` - ${f.relativePath}`));
|
|
23
|
+
console.log();
|
|
24
|
+
|
|
25
|
+
// Compile
|
|
26
|
+
compiler.compile(files);
|
|
27
|
+
}
|
package/src/cli/dev.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Scanner } from "../core/scanner";
|
|
2
|
+
import { ExpressServer } from "../server/expressServer";
|
|
3
|
+
import * as chokidar from "chokidar";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dev command - starts dev server with hot reload
|
|
8
|
+
*/
|
|
9
|
+
export async function dev(port: number = 3000, rootDir: string = process.cwd()): Promise<void> {
|
|
10
|
+
console.log("🚀 Starting kastrick dev server...\n");
|
|
11
|
+
|
|
12
|
+
const scanner = new Scanner(rootDir);
|
|
13
|
+
const server = new ExpressServer(port);
|
|
14
|
+
|
|
15
|
+
// Initial register
|
|
16
|
+
const files = scanner.scanApiFolder();
|
|
17
|
+
if (files.length > 0) {
|
|
18
|
+
server.registerRoutes(files);
|
|
19
|
+
} else {
|
|
20
|
+
console.log("⚠️ No API files found. Create files in /api folder.\n");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
server.start();
|
|
24
|
+
|
|
25
|
+
// Watch for changes
|
|
26
|
+
const apiPath = path.join(rootDir, "api");
|
|
27
|
+
const watcher = chokidar.watch(apiPath, {
|
|
28
|
+
ignored: /(^|[\/\\])\.|node_modules/,
|
|
29
|
+
persistent: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
watcher.on("change", () => {
|
|
33
|
+
console.log("\n♻️ Files changed, reloading routes...\n");
|
|
34
|
+
const updatedFiles = scanner.scanApiFolder();
|
|
35
|
+
server.registerRoutes(updatedFiles);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
watcher.on("add", () => {
|
|
39
|
+
console.log("\n♻️ Files added, reloading routes...\n");
|
|
40
|
+
const updatedFiles = scanner.scanApiFolder();
|
|
41
|
+
server.registerRoutes(updatedFiles);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
watcher.on("unlink", () => {
|
|
45
|
+
console.log("\n♻️ Files removed, reloading routes...\n");
|
|
46
|
+
const updatedFiles = scanner.scanApiFolder();
|
|
47
|
+
server.registerRoutes(updatedFiles);
|
|
48
|
+
});
|
|
49
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { compile } from "./compile";
|
|
4
|
+
import { dev } from "./dev";
|
|
5
|
+
import { serve } from "./serve";
|
|
6
|
+
|
|
7
|
+
const command = process.argv[2];
|
|
8
|
+
const port = process.argv[3] ? parseInt(process.argv[3]) : undefined;
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
try {
|
|
12
|
+
switch (command) {
|
|
13
|
+
case "compile":
|
|
14
|
+
await compile();
|
|
15
|
+
break;
|
|
16
|
+
case "dev":
|
|
17
|
+
await dev(port);
|
|
18
|
+
break;
|
|
19
|
+
case "serve":
|
|
20
|
+
await serve(port);
|
|
21
|
+
break;
|
|
22
|
+
case "--version":
|
|
23
|
+
case "-v":
|
|
24
|
+
console.log("kastrick v1.0.0");
|
|
25
|
+
break;
|
|
26
|
+
case "--help":
|
|
27
|
+
case "-h":
|
|
28
|
+
showHelp();
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
console.log("❌ Unknown command:", command);
|
|
32
|
+
showHelp();
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
} catch (err: any) {
|
|
36
|
+
console.error("❌ Error:", err.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function showHelp() {
|
|
42
|
+
console.log(`
|
|
43
|
+
kastrick - API Framework and Compiler
|
|
44
|
+
|
|
45
|
+
USAGE:
|
|
46
|
+
npx kastrick <command> [port]
|
|
47
|
+
|
|
48
|
+
COMMANDS:
|
|
49
|
+
dev <port> Start dev server with hot reload (default: 3000)
|
|
50
|
+
compile Compile API files to /output folder
|
|
51
|
+
serve <port> Run compiled Express server (default: 3000)
|
|
52
|
+
--help, -h Show this help message
|
|
53
|
+
--version, -v Show version
|
|
54
|
+
|
|
55
|
+
EXAMPLES:
|
|
56
|
+
npx kastrick dev # Start dev server on port 3000
|
|
57
|
+
npx kastrick dev 5000 # Start dev server on port 5000
|
|
58
|
+
npx kastrick compile # Compile to /output
|
|
59
|
+
npx kastrick serve # Run compiled app on port 3000
|
|
60
|
+
npx kastrick serve 8080 # Run compiled app on port 8080
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main();
|
package/src/cli/serve.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Serve command - runs compiled Express app from /output folder
|
|
7
|
+
*/
|
|
8
|
+
export async function serve(port: number = 3000, rootDir: string = process.cwd()): Promise<void> {
|
|
9
|
+
const outputDir = path.join(rootDir, "output");
|
|
10
|
+
const manifestPath = path.join(outputDir, "manifest.json");
|
|
11
|
+
|
|
12
|
+
// Check if compiled output exists
|
|
13
|
+
if (!fs.existsSync(outputDir)) {
|
|
14
|
+
console.log("❌ /output folder not found. Run 'npx kastrick compile' first.\n");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(manifestPath)) {
|
|
19
|
+
console.log("❌ manifest.json not found. Run 'npx kastrick compile' first.\n");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log("🚀 Starting compiled kastrick server...\n");
|
|
24
|
+
|
|
25
|
+
const app = express();
|
|
26
|
+
app.use(express.json());
|
|
27
|
+
app.use(express.urlencoded({ extended: true }));
|
|
28
|
+
|
|
29
|
+
// Logging middleware
|
|
30
|
+
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
31
|
+
console.log(`📡 ${req.method} ${req.path}`);
|
|
32
|
+
next();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Load manifest
|
|
36
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
37
|
+
|
|
38
|
+
// Register routes from manifest
|
|
39
|
+
let routeCount = 0;
|
|
40
|
+
for (const route of manifest.routes || []) {
|
|
41
|
+
const handlerPath = path.join(outputDir, "handlers", route.file);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(handlerPath)) {
|
|
44
|
+
console.warn(`⚠️ Handler not found: ${handlerPath}`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const method = route.method.toLowerCase() as keyof express.Application;
|
|
49
|
+
const routePath = route.path;
|
|
50
|
+
|
|
51
|
+
app[method](routePath, async (req: express.Request, res: express.Response) => {
|
|
52
|
+
try {
|
|
53
|
+
// Dynamically require the handler
|
|
54
|
+
const modulePath = path.resolve(handlerPath);
|
|
55
|
+
delete require.cache[modulePath];
|
|
56
|
+
const handler = require(modulePath);
|
|
57
|
+
|
|
58
|
+
// Call the appropriate method
|
|
59
|
+
const handlerFn = handler[route.method];
|
|
60
|
+
if (!handlerFn) {
|
|
61
|
+
return res.status(404).json({ error: `${route.method} handler not found` });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = await handlerFn(req, { params: req.params });
|
|
65
|
+
res.status(result.status || 200).json(result.body !== undefined ? result.body : result);
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
console.error(`❌ Handler error in ${route.file}:`, err.message);
|
|
68
|
+
res.status(500).json({ error: err.message || "Internal server error" });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
routeCount++;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`📡 Registered ${routeCount} routes from manifest\n`);
|
|
76
|
+
|
|
77
|
+
// Start server
|
|
78
|
+
app.listen(port, () => {
|
|
79
|
+
console.log(`🚀 Compiled server running on http://localhost:${port}\n`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as ts from "typescript";
|
|
4
|
+
import { ApiFile } from "./scanner";
|
|
5
|
+
import { ApiRoute, Router } from "./router";
|
|
6
|
+
|
|
7
|
+
export interface ManifestRoute {
|
|
8
|
+
method: string;
|
|
9
|
+
path: string;
|
|
10
|
+
file: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Manifest {
|
|
14
|
+
routes: ManifestRoute[];
|
|
15
|
+
version: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compiler - generates the /output folder with compiled handlers
|
|
21
|
+
*/
|
|
22
|
+
export class Compiler {
|
|
23
|
+
private outputDir: string;
|
|
24
|
+
private handlersDir: string;
|
|
25
|
+
private router: Router;
|
|
26
|
+
|
|
27
|
+
constructor(rootDir: string = process.cwd()) {
|
|
28
|
+
this.outputDir = path.join(rootDir, "output");
|
|
29
|
+
this.handlersDir = path.join(this.outputDir, "handlers");
|
|
30
|
+
this.router = new Router();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compile API files to output folder
|
|
35
|
+
*/
|
|
36
|
+
compile(files: ApiFile[]): boolean {
|
|
37
|
+
try {
|
|
38
|
+
// Create output directory structure
|
|
39
|
+
this.ensureDirectories();
|
|
40
|
+
|
|
41
|
+
// Generate routes
|
|
42
|
+
const routes = this.router.generateRoutes(files);
|
|
43
|
+
|
|
44
|
+
// Copy handler files
|
|
45
|
+
this.copyHandlers(files);
|
|
46
|
+
|
|
47
|
+
// Generate manifest
|
|
48
|
+
this.generateManifest(routes);
|
|
49
|
+
|
|
50
|
+
// Generate app.js (Express app)
|
|
51
|
+
this.generateAppJs(routes);
|
|
52
|
+
|
|
53
|
+
console.log(`✅ Compilation successful! Output: ${this.outputDir}`);
|
|
54
|
+
console.log(`📝 Routes registered: ${routes.length}`);
|
|
55
|
+
return true;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`❌ Compilation failed:`, error);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private ensureDirectories(): void {
|
|
63
|
+
if (!fs.existsSync(this.outputDir)) {
|
|
64
|
+
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
if (!fs.existsSync(this.handlersDir)) {
|
|
67
|
+
fs.mkdirSync(this.handlersDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private copyHandlers(files: ApiFile[]): void {
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
// Change .ts to .js in output
|
|
74
|
+
let targetPath = path.join(this.handlersDir, file.relativePath);
|
|
75
|
+
if (targetPath.endsWith(".ts")) {
|
|
76
|
+
targetPath = targetPath.slice(0, -3) + ".js";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const targetDir = path.dirname(targetPath);
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(targetDir)) {
|
|
82
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Transpile TypeScript to JavaScript
|
|
86
|
+
if (file.relativePath.endsWith(".ts")) {
|
|
87
|
+
const transpiled = ts.transpileModule(file.content || "", {
|
|
88
|
+
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
89
|
+
});
|
|
90
|
+
fs.writeFileSync(targetPath, transpiled.outputText, "utf-8");
|
|
91
|
+
} else {
|
|
92
|
+
// Plain JavaScript files - copy as-is
|
|
93
|
+
fs.writeFileSync(targetPath, file.content || "", "utf-8");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private generateManifest(routes: ApiRoute[]): void {
|
|
99
|
+
const manifest: Manifest = {
|
|
100
|
+
version: "1.0.0",
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
routes: routes.map((route) => ({
|
|
103
|
+
method: route.method,
|
|
104
|
+
path: route.path,
|
|
105
|
+
file: route.file,
|
|
106
|
+
})),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const manifestPath = path.join(this.outputDir, "manifest.json");
|
|
110
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private generateAppJs(routes: ApiRoute[]): void {
|
|
114
|
+
const routeCode = routes
|
|
115
|
+
.map((route) => {
|
|
116
|
+
const methodLower = route.method.toLowerCase();
|
|
117
|
+
const importPath = `./${path.join("handlers", route.file).replace(/\\/g, "/")}`;
|
|
118
|
+
return `app.${methodLower}('${route.path}', async (req, res) => {
|
|
119
|
+
try {
|
|
120
|
+
const handler = require('${importPath}');
|
|
121
|
+
const fn = handler.${route.method};
|
|
122
|
+
if (!fn) return res.status(404).json({ error: 'Handler not found' });
|
|
123
|
+
|
|
124
|
+
const result = await fn(req, { params: req.params });
|
|
125
|
+
res.status(result.status || 200).json(result.body || result);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
res.status(500).json({ error: err.message });
|
|
128
|
+
}
|
|
129
|
+
});`;
|
|
130
|
+
})
|
|
131
|
+
.join("\n\n");
|
|
132
|
+
|
|
133
|
+
const appCode = `const express = require('express');
|
|
134
|
+
const app = express();
|
|
135
|
+
|
|
136
|
+
app.use(express.json());
|
|
137
|
+
|
|
138
|
+
// Routes from manifest
|
|
139
|
+
${routeCode}
|
|
140
|
+
|
|
141
|
+
module.exports = app;
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
const appPath = path.join(this.outputDir, "app.js");
|
|
145
|
+
fs.writeFileSync(appPath, appCode, "utf-8");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { ApiFile } from "./scanner";
|
|
3
|
+
|
|
4
|
+
export interface ApiRoute {
|
|
5
|
+
method: string;
|
|
6
|
+
path: string;
|
|
7
|
+
file: string;
|
|
8
|
+
handlerName: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Router - converts file structure to API routes
|
|
13
|
+
* Maps:
|
|
14
|
+
* - api/hello.js -> /hello (GET)
|
|
15
|
+
* - api/users/index.js -> /users (GET)
|
|
16
|
+
* - api/users/[id].js -> /users/:id (GET)
|
|
17
|
+
*/
|
|
18
|
+
export class Router {
|
|
19
|
+
/**
|
|
20
|
+
* Convert file path to route path
|
|
21
|
+
* Handles dynamic segments [id] -> :id
|
|
22
|
+
*/
|
|
23
|
+
filePathToRoute(relativePath: string): string {
|
|
24
|
+
// Remove extension
|
|
25
|
+
const withoutExt = relativePath.replace(/\.(js|ts)$/, "");
|
|
26
|
+
|
|
27
|
+
// Split into parts
|
|
28
|
+
let parts = withoutExt.split(path.sep);
|
|
29
|
+
|
|
30
|
+
// Remove 'index' filename, keep directory
|
|
31
|
+
if (parts[parts.length - 1] === "index") {
|
|
32
|
+
parts = parts.slice(0, -1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Convert [id] to :id (dynamic segments)
|
|
36
|
+
parts = parts.map((part) => {
|
|
37
|
+
if (part.startsWith("[") && part.endsWith("]")) {
|
|
38
|
+
return `:${part.slice(1, -1)}`;
|
|
39
|
+
}
|
|
40
|
+
return part;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Build route path
|
|
44
|
+
const route = "/" + parts.join("/");
|
|
45
|
+
return route === "/" ? "/" : route;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract HTTP methods from file content
|
|
50
|
+
* Looks for exports: GET, POST, PUT, DELETE, etc.
|
|
51
|
+
*/
|
|
52
|
+
extractMethods(content: string): string[] {
|
|
53
|
+
const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
54
|
+
const foundMethods: string[] = [];
|
|
55
|
+
|
|
56
|
+
for (const method of methods) {
|
|
57
|
+
if (new RegExp(`export\\s+function\\s+${method}\\s*\\(|export\\s+const\\s+${method}\\s*=`).test(content)) {
|
|
58
|
+
foundMethods.push(method);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Default to GET if no methods found
|
|
63
|
+
return foundMethods.length > 0 ? foundMethods : ["GET"];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate routes from API files
|
|
68
|
+
*/
|
|
69
|
+
generateRoutes(files: ApiFile[]): ApiRoute[] {
|
|
70
|
+
const routes: ApiRoute[] = [];
|
|
71
|
+
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
const routePath = this.filePathToRoute(file.relativePath);
|
|
74
|
+
const methods = this.extractMethods(file.content || "");
|
|
75
|
+
|
|
76
|
+
for (const method of methods) {
|
|
77
|
+
routes.push({
|
|
78
|
+
method,
|
|
79
|
+
path: routePath,
|
|
80
|
+
file: file.relativePath,
|
|
81
|
+
handlerName: method,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return routes;
|
|
87
|
+
}
|
|
88
|
+
}
|