aaex-cli 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.
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs-extra";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ // -------------------------
8
+ // Helpers for ESM __dirname
9
+ // -------------------------
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ // -------------------------
14
+ // Parse CLI arguments
15
+ // -------------------------
16
+ const args = process.argv.slice(2);
17
+ if (!args[0]) {
18
+ console.error("Usage: create-aaex-app <project-name>");
19
+ process.exit(1);
20
+ }
21
+ const appName = args[0];
22
+ const targetDir = path.resolve(process.cwd(), appName);
23
+
24
+ // -------------------------
25
+ // Paths inside the package
26
+ // -------------------------
27
+ const templateDir = path.resolve(__dirname, "template");
28
+
29
+ // -------------------------
30
+ // Copy template files
31
+ // -------------------------
32
+ async function copyTemplate() {
33
+ try {
34
+ console.log(`Creating new AAEx app in ${targetDir}...`);
35
+
36
+ await fs.copy(templateDir, targetDir, {
37
+ overwrite: true,
38
+ errorOnExist: false,
39
+ });
40
+
41
+ console.log("Template copied successfully.");
42
+ } catch (err) {
43
+ console.error("Error copying template:", err);
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ // -------------------------
49
+ // Create package.json
50
+ // -------------------------
51
+ async function createPackageJson() {
52
+ const pkg = {
53
+ name: appName,
54
+ version: "0.0.0",
55
+ private: true,
56
+ type: "module",
57
+ scripts: {
58
+ dev: "node .aaex/server/server.js",
59
+ build:
60
+ "npm run build:client && npm run build:server && npm run build:api",
61
+ "build:client": "vite build --outDir dist/client",
62
+ "build:server":
63
+ "vite build --ssr .aaex/framework/server-entry.tsx --outDir dist/server",
64
+ "build:api": "tsc --project tsconfig.api.json",
65
+ preview: "cross-env NODE_ENV=production node .aaex/server/server.js",
66
+ },
67
+ dependencies: {
68
+ react: "^19.1.1",
69
+ "react-dom": "^19.1.1",
70
+ "react-router": "^7.10.0",
71
+ "react-router-dom": "^7.10.0",
72
+ express: "^5.1.0",
73
+ compression: "^1.8.1",
74
+ sirv: "^3.0.2",
75
+ "aaex-file-router": "^1.4.4",
76
+ },
77
+ devDependencies: {
78
+ typescript: "~5.9.2",
79
+ "@types/react": "^19.1.13",
80
+ "@types/react-dom": "^19.1.9",
81
+ "@types/express": "^5.0.3",
82
+ "@vitejs/plugin-react": "^5.0.2",
83
+ vite: "^7.1.5",
84
+ "cross-env": "^10.0.0",
85
+ },
86
+ };
87
+
88
+ try {
89
+ await fs.writeJson(path.join(targetDir, "package.json"), pkg, {
90
+ spaces: 2,
91
+ });
92
+ console.log("package.json created.");
93
+ } catch (err) {
94
+ console.error("Error creating package.json:", err);
95
+ process.exit(1);
96
+ }
97
+ }
98
+
99
+ // -------------------------
100
+ // Run
101
+ // -------------------------
102
+ async function main() {
103
+ if (fs.existsSync(targetDir)) {
104
+ console.error(`Folder ${appName} already exists. Aborting.`);
105
+ process.exit(1);
106
+ }
107
+
108
+ await copyTemplate();
109
+ await createPackageJson();
110
+
111
+ console.log("\n AAEx app created successfully!");
112
+ console.log(`\nNext steps:\n cd ${appName}\n npm install\n npm run dev`);
113
+ }
114
+
115
+ main();
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "aaex-cli",
3
+ "version": "1.0.0",
4
+ "description": "Command line interface for creating aaexjs app",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "module",
8
+ "bin": {
9
+ "create-aaex-app": "./create-aaex-app.js"
10
+ },
11
+ "scripts": {
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^5.1.1",
16
+ "aaex-file-router": "^1.4.5",
17
+ "react": "^19.2.1",
18
+ "react-dom": "^19.2.1",
19
+ "react-router": "^7.10.1",
20
+ "typescript": "^5.9.3",
21
+ "vite": "^7.2.6"
22
+ },
23
+ "dependencies": {
24
+ "fs-extra": "^11.3.2"
25
+ }
26
+ }
@@ -0,0 +1,53 @@
1
+ import { FileScanner } from "aaex-file-router/core";
2
+ import { match } from "path-to-regexp";
3
+
4
+ // --- Scan API folder ---
5
+ const apiScanner = new FileScanner("./src/api");
6
+
7
+ const files = await apiScanner.get_file_data();
8
+
9
+ const routes = BuildApiRoutes(files);
10
+
11
+ export default routes;
12
+
13
+ // --- Build routes from file tree ---
14
+ function BuildApiRoutes(files) {
15
+ const routes = [];
16
+
17
+ function walk(node, currentPath) {
18
+ if (node.isDirectory) {
19
+ node.children?.forEach((child) => {
20
+ walk(child, currentPath + "/" + node.name);
21
+ });
22
+ return;
23
+ }
24
+
25
+ const filePath = currentPath + "/" + node.name;
26
+
27
+ let route = filePath
28
+ .replace(/^.*src\/api/, "/api") // replace root
29
+ .replace(/\.ts$/, "") // remove extension
30
+ .replace(/\[(.+?)\]/g, ":$1") // dynamic params
31
+ .replace("/index", ""); // remove index from end
32
+
33
+ routes.push({
34
+ route,
35
+ filePath: node.relative_path,
36
+ });
37
+ }
38
+
39
+ files.forEach((f) => walk(f, ""));
40
+ return routes;
41
+ }
42
+
43
+ // --- Match path to route ---
44
+ export function PathToRoute(path, routes) {
45
+ for (const r of routes) {
46
+ const matcher = match(r.route, { decode: decodeURIComponent });
47
+ const matched = matcher(path); // pass full path including /api
48
+ if (matched) {
49
+ return { route: r, params: matched.params }; // return params for dynamic segments
50
+ }
51
+ }
52
+ return null;
53
+ }
@@ -0,0 +1,16 @@
1
+ import "../../src/index.css"
2
+ import { StrictMode, Suspense } from "react";
3
+ import { hydrateRoot } from "react-dom/client";
4
+ import App from "../../src/App"
5
+ import { BrowserRouter } from "react-router";
6
+
7
+ hydrateRoot(
8
+ document.getElementById("root") as HTMLElement,
9
+ <StrictMode>
10
+ <BrowserRouter>
11
+ <Suspense>
12
+ <App />
13
+ </Suspense>
14
+ </BrowserRouter>
15
+ </StrictMode>
16
+ );
@@ -0,0 +1,21 @@
1
+ import { StrictMode, Suspense } from "react";
2
+ import { renderToString } from "react-dom/server";
3
+ import { StaticRouter } from "react-router";
4
+ import App from "../../src/App";
5
+
6
+ export function render(_url: string) {
7
+ const url = `/${_url}`;
8
+
9
+ // call your SSR function or API here and pass the result as props
10
+
11
+ const html = renderToString(
12
+ <StrictMode>
13
+ <StaticRouter location={url}>
14
+ <Suspense>
15
+ <App />
16
+ </Suspense>
17
+ </StaticRouter>
18
+ </StrictMode>
19
+ );
20
+ return { html };
21
+ }
@@ -0,0 +1,134 @@
1
+ import fs from "node:fs/promises";
2
+ import express from "express";
3
+ import { pathToFileURL } from "node:url";
4
+ import path from "node:path";
5
+
6
+ // server.js is now in .aaex/server/
7
+ const frameworkRoot = path.resolve(".aaex"); // absolute path to .aaex
8
+ const projectRoot = path.resolve("."); // root of the project
9
+
10
+ // Import BuildApiRoutes
11
+ const { default: apiRoutes, PathToRoute } = await import(
12
+ pathToFileURL(`${frameworkRoot}/BuildApiRoutes.js`).href
13
+ );
14
+
15
+ // Constants
16
+ const isProduction = process.env.NODE_ENV === "production";
17
+ const port = process.env.PORT || 5173;
18
+ const base = process.env.BASE || "/";
19
+
20
+ // Cached production HTML
21
+ const templateHtml = isProduction
22
+ ? await fs.readFile(path.join(projectRoot, "dist/client/index.html"), "utf-8")
23
+ : "";
24
+
25
+ // Create HTTP server
26
+ const app = express();
27
+
28
+ /** @type {import('vite').ViteDevServer | undefined} */
29
+ let vite;
30
+ if (!isProduction) {
31
+ const { createServer } = await import("vite");
32
+ vite = await createServer({
33
+ server: { middlewareMode: true },
34
+ appType: "custom",
35
+ root: projectRoot,
36
+ base,
37
+ });
38
+ app.use(vite.middlewares);
39
+ } else {
40
+ const compression = (await import("compression")).default;
41
+ const sirv = (await import("sirv")).default;
42
+ app.use(compression());
43
+ app.use(
44
+ base,
45
+ sirv(path.join(projectRoot, "dist/client"), { extensions: [] })
46
+ );
47
+ }
48
+
49
+ // API routing
50
+ app.use("/api", async (req, res) => {
51
+ console.log(req.path);
52
+
53
+ const routeMatch = PathToRoute(req.path, apiRoutes);
54
+
55
+ console.log(routeMatch);
56
+
57
+ if (!routeMatch)
58
+ return res.status(404).json({ error: "API route not found" });
59
+
60
+ const { route, params } = routeMatch;
61
+ let modulePath;
62
+
63
+ if (!isProduction) {
64
+ // DEV: Vite handles TS/JS loading
65
+ modulePath = `/src/api${route.filePath.replace(/^src\/api/, "")}`;
66
+ } else {
67
+ // PROD: bundled JS
68
+ modulePath = pathToFileURL(
69
+ `./dist/server/api${route.filePath
70
+ .replace(/^src\/api/, "")
71
+ .replace(/\.ts$/, ".js")}`
72
+ ).href;
73
+ }
74
+
75
+ try {
76
+ const mod = !isProduction
77
+ ? await vite.ssrLoadModule(modulePath)
78
+ : await import(modulePath);
79
+
80
+ const handler = mod[req.method]; // GET, POST, etc.
81
+
82
+ if (typeof handler !== "function") {
83
+ return res.status(405).json({ error: "Method not allowed" });
84
+ }
85
+
86
+ const result = await handler(req, res, params);
87
+
88
+ if (!res.headersSent) res.json(result);
89
+ } catch (err) {
90
+ console.error("API load error:", err);
91
+ res.status(500).json({ error: "Internal Server Error" });
92
+ }
93
+ });
94
+
95
+ // SSR HTML
96
+ app.use("*all", async (req, res) => {
97
+ try {
98
+ const url = req.originalUrl.replace(base, "");
99
+
100
+ let template;
101
+ let render;
102
+ if (!isProduction) {
103
+ template = await fs.readFile(
104
+ path.join(projectRoot, "index.html"),
105
+ "utf-8"
106
+ );
107
+ template = await vite.transformIndexHtml(url, template);
108
+ render = (await vite.ssrLoadModule("/.aaex/framework/entry-server.tsx"))
109
+ .render;
110
+ } else {
111
+ template = templateHtml;
112
+ render = (
113
+ await import(path.join(projectRoot, "dist/server/entry-server.js"))
114
+ ).render;
115
+ }
116
+
117
+ const rendered = await render(url);
118
+
119
+ const html = template
120
+ .replace("<!--app-head-->", rendered.head ?? "")
121
+ .replace("<!--app-html-->", rendered.html ?? "");
122
+
123
+ res.status(200).set({ "Content-Type": "text/html" }).send(html);
124
+ } catch (e) {
125
+ vite?.ssrFixStacktrace?.(e);
126
+ console.error(e);
127
+ res.status(500).send(e.stack);
128
+ }
129
+ });
130
+
131
+ // Start server
132
+ app.listen(port, () => {
133
+ console.log(`Server started at http://localhost:${port}`);
134
+ });
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>AaExJS</title>
8
+ <!--app-head-->
9
+ </head>
10
+ <body>
11
+ <div id="root"><!--app-html--></div>
12
+ <script type="module" src="/.aaex/framework/entry-client.tsx"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,41 @@
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ }
13
+ .logo:hover {
14
+ filter: drop-shadow(0 0 2em #646cffaa);
15
+ }
16
+ .logo.react:hover {
17
+ filter: drop-shadow(0 0 2em #61dafbaa);
18
+ }
19
+
20
+ @keyframes logo-spin {
21
+ from {
22
+ transform: rotate(0deg);
23
+ }
24
+ to {
25
+ transform: rotate(360deg);
26
+ }
27
+ }
28
+
29
+ @media (prefers-reduced-motion: no-preference) {
30
+ a:nth-of-type(2) .logo {
31
+ animation: logo-spin infinite 20s linear;
32
+ }
33
+ }
34
+
35
+ .card {
36
+ padding: 2em;
37
+ }
38
+
39
+ .read-the-docs {
40
+ color: #888;
41
+ }
@@ -0,0 +1,23 @@
1
+ import "./App.css";
2
+ import { Route, RouteObject, Routes } from "react-router";
3
+ import routes from "./routes";
4
+
5
+ function renderRoutes(routesArray: RouteObject[]) {
6
+ return routesArray.map((route) => {
7
+ if (route.children && route.children.length > 0) {
8
+ return (
9
+ <Route path={route.path} element={route.element}>
10
+ {renderRoutes(route.children)}
11
+ </Route>
12
+ );
13
+ } else {
14
+ return <Route path={route.path} element={route.element} />;
15
+ }
16
+ });
17
+ }
18
+
19
+ function App() {
20
+ return <Routes>{renderRoutes(routes)}</Routes>;
21
+ }
22
+
23
+ export default App;
@@ -0,0 +1,3 @@
1
+ export const GET = async () => {
2
+ return { hello: "world" };
3
+ };
@@ -0,0 +1,69 @@
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ -webkit-text-size-adjust: 100%;
15
+ }
16
+
17
+ a {
18
+ font-weight: 500;
19
+ color: #646cff;
20
+ text-decoration: inherit;
21
+ }
22
+ a:hover {
23
+ color: #535bf2;
24
+ }
25
+
26
+ body {
27
+ margin: 0;
28
+ display: flex;
29
+ place-items: center;
30
+ min-width: 320px;
31
+ min-height: 100vh;
32
+ }
33
+
34
+ h1 {
35
+ font-size: 3.2em;
36
+ line-height: 1.1;
37
+ }
38
+
39
+ button {
40
+ border-radius: 8px;
41
+ border: 1px solid transparent;
42
+ padding: 0.6em 1.2em;
43
+ font-size: 1em;
44
+ font-weight: 500;
45
+ font-family: inherit;
46
+ background-color: #1a1a1a;
47
+ cursor: pointer;
48
+ transition: border-color 0.25s;
49
+ }
50
+ button:hover {
51
+ border-color: #646cff;
52
+ }
53
+ button:focus,
54
+ button:focus-visible {
55
+ outline: 4px auto -webkit-focus-ring-color;
56
+ }
57
+
58
+ @media (prefers-color-scheme: light) {
59
+ :root {
60
+ color: #213547;
61
+ background-color: #ffffff;
62
+ }
63
+ a:hover {
64
+ color: #747bff;
65
+ }
66
+ button {
67
+ background-color: #f9f9f9;
68
+ }
69
+ }
@@ -0,0 +1,3 @@
1
+ export default function Home() {
2
+ return <>Hello AaEx</>;
3
+ }
@@ -0,0 +1,13 @@
1
+ //* AUTO GENERATED: DO NOT EDIT
2
+ import React from 'react';
3
+ import Index from './pages/index.tsx';
4
+ import type { RouteObject } from 'react-router-dom';
5
+
6
+ const routes: RouteObject[] = [
7
+ {
8
+ "path": "",
9
+ "element": React.createElement(Index)
10
+ }
11
+ ];
12
+
13
+ export default routes;
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import { aaexFileRouter } from 'aaex-file-router/plugin'
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react(),aaexFileRouter()],
8
+ })