aaexjs-test 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/dist/components/ClientOnly.d.ts +6 -0
- package/dist/components/ClientOnly.d.ts.map +1 -0
- package/dist/components/ClientOnly.js +12 -0
- package/dist/components/ClientOnly.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/ServerLoadCssImports.d.ts +3 -0
- package/dist/utils/ServerLoadCssImports.d.ts.map +1 -0
- package/dist/utils/ServerLoadCssImports.js +45 -0
- package/dist/utils/ServerLoadCssImports.js.map +1 -0
- package/dist/utils/cookies.d.ts +13 -0
- package/dist/utils/cookies.d.ts.map +1 -0
- package/dist/utils/cookies.js +47 -0
- package/dist/utils/cookies.js.map +1 -0
- package/package.json +36 -0
- package/scripts/postinstall.js +103 -0
- package/templates/.aaex/BuildApiRoutes.js +67 -0
- package/templates/.aaex/api/auth/login.ts +57 -0
- package/templates/.aaex/api/auth/register.ts +34 -0
- package/templates/.aaex/api/auth/validate.ts +18 -0
- package/templates/.aaex/framework/database/mongodb.ts +29 -0
- package/templates/.aaex/framework/entry-client.tsx +16 -0
- package/templates/.aaex/framework/entry-server.tsx +18 -0
- package/templates/.aaex/matchServerRoutes.js +60 -0
- package/templates/.aaex/server/server.js +179 -0
- package/templates/.aaex/version.json +4 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
interface ClientWrapperProps extends React.PropsWithChildren {
|
|
2
|
+
fallback?: React.ReactNode | React.ReactNode[];
|
|
3
|
+
}
|
|
4
|
+
export declare function ClientOnly({ children, fallback, }: ClientWrapperProps): import("react").ReactNode;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=ClientOnly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientOnly.d.ts","sourceRoot":"","sources":["../../src/components/ClientOnly.tsx"],"names":[],"mappings":"AAEA,UAAU,kBAAmB,SAAQ,KAAK,CAAC,iBAAiB;IAC1D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;CAChD;AAED,wBAAiB,UAAU,CAAC,EAC1B,QAAQ,EACR,QAAe,GAChB,EAAE,kBAAkB,6BAWpB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
export function ClientOnly({ children, fallback = null, }) {
|
|
3
|
+
const [mounted, setMounted] = useState(false);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
setMounted(true);
|
|
6
|
+
}, []);
|
|
7
|
+
if (mounted) {
|
|
8
|
+
return children;
|
|
9
|
+
}
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=ClientOnly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientOnly.js","sourceRoot":"","sources":["../../src/components/ClientOnly.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAM5C,MAAM,UAAW,UAAU,CAAC,EAC1B,QAAQ,EACR,QAAQ,GAAG,IAAI,GACI;IACnB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE9C,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServerLoadCssImports.d.ts","sourceRoot":"","sources":["../../src/utils/ServerLoadCssImports.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,MAAM,EAAgB,MAAM,MAAM,CAAC;AAMhD,wBAAgB,mBAAmB,IAAI,MAAM,CA0C5C"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//credit to https://github.com/vitejs/vite/issues/16515
|
|
2
|
+
const virtualCssPath = '/@virtual:ssr-css.css';
|
|
3
|
+
const collectedStyles = new Map();
|
|
4
|
+
export function pluginSsrDevFoucFix() {
|
|
5
|
+
let server;
|
|
6
|
+
return {
|
|
7
|
+
name: 'ssr-dev-FOUC-fix',
|
|
8
|
+
apply: 'serve',
|
|
9
|
+
transform(code, id) {
|
|
10
|
+
if (id.includes('node_modules'))
|
|
11
|
+
return null;
|
|
12
|
+
if (id.includes('.css')) {
|
|
13
|
+
collectedStyles.set(id, code);
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
},
|
|
17
|
+
configureServer(server_) {
|
|
18
|
+
server = server_;
|
|
19
|
+
server.middlewares.use((req, _res, next) => {
|
|
20
|
+
if (req.url === virtualCssPath) {
|
|
21
|
+
_res.setHeader('Content-Type', 'text/css');
|
|
22
|
+
_res.write(Array.from(collectedStyles.values()).join('\n'));
|
|
23
|
+
_res.end();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
next();
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
transformIndexHtml: {
|
|
30
|
+
handler: async () => {
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
tag: 'link',
|
|
34
|
+
injectTo: 'head',
|
|
35
|
+
attrs: {
|
|
36
|
+
rel: 'stylesheet',
|
|
37
|
+
href: virtualCssPath,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=ServerLoadCssImports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServerLoadCssImports.js","sourceRoot":"","sources":["../../src/utils/ServerLoadCssImports.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAIvD,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAE/C,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;AAElD,MAAM,UAAU,mBAAmB;IACjC,IAAI,MAAqB,CAAC;IAE1B,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,OAAO;QACd,SAAS,CAAC,IAAY,EAAE,EAAU;YAChC,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC7C,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,eAAe,CAAC,OAAO;YACrB,MAAM,GAAG,OAAO,CAAC;YAEjB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBACzC,IAAI,GAAG,CAAC,GAAG,KAAK,cAAc,EAAE,CAAC;oBAC/B,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;oBAC3C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;oBAC5D,IAAI,CAAC,GAAG,EAAE,CAAC;oBACX,OAAO;gBACT,CAAC;gBACD,IAAI,EAAE,CAAC;YACT,CAAC,CAAC,CAAC;QACL,CAAC;QAED,kBAAkB,EAAE;YAClB,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,OAAO;oBACL;wBACE,GAAG,EAAE,MAAM;wBACX,QAAQ,EAAE,MAAM;wBAChB,KAAK,EAAE;4BACL,GAAG,EAAE,YAAY;4BACjB,IAAI,EAAE,cAAc;yBACrB;qBACF;iBACF,CAAC;YACJ,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function setCookie(name: string, value: string, days?: number, options?: {
|
|
2
|
+
path?: string;
|
|
3
|
+
secure?: boolean;
|
|
4
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
5
|
+
domain?: string;
|
|
6
|
+
}): void;
|
|
7
|
+
export declare function getCookie(name: string): string | null;
|
|
8
|
+
export declare function getCookieAsJSON(name: string): Record<string, string> | null | undefined;
|
|
9
|
+
export declare function deleteCookie(name: string, options?: {
|
|
10
|
+
path?: string;
|
|
11
|
+
domain?: string;
|
|
12
|
+
}): void;
|
|
13
|
+
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/utils/cookies.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,SAAI,EACR,OAAO,GAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,QAcP;AACD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,iBAWrC;AAiBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,6CAI3C;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,QAKjD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function setCookie(name, value, days = 7, options = {}) {
|
|
2
|
+
const maxAge = days * 24 * 60 * 60;
|
|
3
|
+
const encoded = encodeURIComponent(value);
|
|
4
|
+
let cookie = `${name}=${encoded}; max-age=${maxAge}; path=${options.path ?? "/"}`;
|
|
5
|
+
if (options.secure)
|
|
6
|
+
cookie += "; secure";
|
|
7
|
+
if (options.sameSite)
|
|
8
|
+
cookie += `; samesite=${options.sameSite}`;
|
|
9
|
+
if (options.domain)
|
|
10
|
+
cookie += `; domain=${options.domain}`;
|
|
11
|
+
document.cookie = cookie;
|
|
12
|
+
}
|
|
13
|
+
export function getCookie(name) {
|
|
14
|
+
const cookies = document.cookie.split("; ");
|
|
15
|
+
for (const c of cookies) {
|
|
16
|
+
const [key, ...rest] = c.split("=");
|
|
17
|
+
if (key === name) {
|
|
18
|
+
return decodeURIComponent(rest.join("="));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function cookieToJSON(cookie) {
|
|
24
|
+
const parts = cookie.split("; ");
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const part of parts) {
|
|
27
|
+
const [key, ...rest] = part.split("=");
|
|
28
|
+
if (!key) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
result[key] = rest.join("=");
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
export function getCookieAsJSON(name) {
|
|
36
|
+
const raw = getCookie(name);
|
|
37
|
+
if (!raw)
|
|
38
|
+
return null;
|
|
39
|
+
return cookieToJSON(raw);
|
|
40
|
+
}
|
|
41
|
+
export function deleteCookie(name, options = {}) {
|
|
42
|
+
let cookie = `${name}=; max-age=0; path=${options.path ?? "/"}`;
|
|
43
|
+
if (options.domain)
|
|
44
|
+
cookie += `; domain=${options.domain}`;
|
|
45
|
+
document.cookie = cookie;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/utils/cookies.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,KAAa,EACb,IAAI,GAAG,CAAC,EACR,UAKI,EAAE;IAEN,MAAM,MAAM,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE1C,IAAI,MAAM,GAAG,GAAG,IAAI,IAAI,OAAO,aAAa,MAAM,UAChD,OAAO,CAAC,IAAI,IAAI,GAClB,EAAE,CAAC;IAEH,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,UAAU,CAAC;IACzC,IAAI,OAAO,CAAC,QAAQ;QAAE,MAAM,IAAI,cAAc,OAAO,CAAC,QAAQ,EAAE,CAAC;IACjE,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,YAAY,OAAO,CAAC,MAAM,EAAE,CAAC;IAE3D,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;AAC3B,CAAC;AACD,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE5C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO;QACT,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,UAA8C,EAAE;IAEhD,IAAI,MAAM,GAAG,GAAG,IAAI,sBAAsB,OAAO,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;IAChE,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,YAAY,OAAO,CAAC,MAAM,EAAE,CAAC;IAC3D,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;AAC3B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aaexjs-test",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"dev": "npx tsc --watch",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"postinstall": "node scripts/postinstall.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"scripts",
|
|
18
|
+
"templates"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^25.0.3",
|
|
29
|
+
"@types/react": "^19.2.7",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vite": "^7.3.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"react": "^19.2.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
/* ------------------------------
|
|
6
|
+
Paths & environment
|
|
7
|
+
-------------------------------- */
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const projectRoot = process.cwd();
|
|
13
|
+
const aaexDir = path.join(projectRoot, ".aaex");
|
|
14
|
+
const templateDir = path.join(__dirname, "../templates/.aaex");
|
|
15
|
+
const versionFile = path.join(aaexDir, "version.json");
|
|
16
|
+
|
|
17
|
+
/* ------------------------------
|
|
18
|
+
Read framework version
|
|
19
|
+
-------------------------------- */
|
|
20
|
+
|
|
21
|
+
function readPackageVersion() {
|
|
22
|
+
try {
|
|
23
|
+
const pkgPath = path.join(__dirname, "../package.json");
|
|
24
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
25
|
+
return pkg.version;
|
|
26
|
+
} catch {
|
|
27
|
+
return "unknown";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const frameworkVersion = readPackageVersion();
|
|
32
|
+
|
|
33
|
+
/* ------------------------------
|
|
34
|
+
Helpers
|
|
35
|
+
-------------------------------- */
|
|
36
|
+
|
|
37
|
+
function readInstalledVersion() {
|
|
38
|
+
if (!fs.existsSync(versionFile)) return null;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const json = JSON.parse(fs.readFileSync(versionFile, "utf-8"));
|
|
42
|
+
return json.version ?? null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeVersionFile() {
|
|
49
|
+
const payload = {
|
|
50
|
+
framework: "aaexjs",
|
|
51
|
+
version: frameworkVersion,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
fs.writeFileSync(versionFile, JSON.stringify(payload, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function copyRecursive(src, dest) {
|
|
58
|
+
if (!fs.existsSync(dest)) {
|
|
59
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
63
|
+
const srcPath = path.join(src, entry.name);
|
|
64
|
+
const destPath = path.join(dest, entry.name);
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
copyRecursive(srcPath, destPath);
|
|
68
|
+
} else {
|
|
69
|
+
fs.copyFileSync(srcPath, destPath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ------------------------------
|
|
75
|
+
Main logic
|
|
76
|
+
-------------------------------- */
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const installedVersion = readInstalledVersion();
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(aaexDir)) {
|
|
82
|
+
console.log("[aaexjs] Creating .aaex directory");
|
|
83
|
+
copyRecursive(templateDir, aaexDir);
|
|
84
|
+
writeVersionFile();
|
|
85
|
+
console.log(`[aaexjs] Installed framework files (v${frameworkVersion})`);
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (installedVersion !== frameworkVersion) {
|
|
90
|
+
console.log(
|
|
91
|
+
`[aaexjs] Updating .aaex from v${installedVersion ?? "unknown"} → v${frameworkVersion}`
|
|
92
|
+
);
|
|
93
|
+
copyRecursive(templateDir, aaexDir);
|
|
94
|
+
writeVersionFile();
|
|
95
|
+
console.log("[aaexjs] Update complete");
|
|
96
|
+
} else {
|
|
97
|
+
// Optional: keep silent to avoid noisy installs
|
|
98
|
+
// console.log("[aaexjs] .aaex is up to date");
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.warn("[aaexjs] Failed to setup .aaex directory");
|
|
102
|
+
console.warn(err);
|
|
103
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { FileScanner } from "aaex-file-router/core";
|
|
2
|
+
import { match } from "path-to-regexp";
|
|
3
|
+
|
|
4
|
+
// --- scan folders ---
|
|
5
|
+
//wrapper for the FilesScanner and output data
|
|
6
|
+
async function scanApiFolder(folder) {
|
|
7
|
+
const scanner = new FileScanner(folder);
|
|
8
|
+
return await scanner.get_file_data();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const userApiFolder = "src/api";
|
|
12
|
+
|
|
13
|
+
const internalApiFolder = ".aaex/api";
|
|
14
|
+
|
|
15
|
+
//collect file data as easily parsed arrays
|
|
16
|
+
const [userFiles, internalFiles] = await Promise.all([
|
|
17
|
+
scanApiFolder(userApiFolder),
|
|
18
|
+
scanApiFolder(internalApiFolder),
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
// --- combine files ---
|
|
22
|
+
const fileMap = new Map();
|
|
23
|
+
internalFiles.forEach((f) => fileMap.set(f.relative_path, f));
|
|
24
|
+
userFiles.forEach((f) => fileMap.set(f.relative_path, f)); //overrides internal api with user defined routes
|
|
25
|
+
const combinedFiles = Array.from(fileMap.values());
|
|
26
|
+
|
|
27
|
+
// --- build route list ---
|
|
28
|
+
/**Builds route object from the scanned files */
|
|
29
|
+
function buildApiRoutes(files) {
|
|
30
|
+
const routes = [];
|
|
31
|
+
|
|
32
|
+
function walk(node, currentPath) {
|
|
33
|
+
//recursivly iterate over child routes
|
|
34
|
+
if (node.isDirectory) {
|
|
35
|
+
node.children?.forEach((c) => walk(c, currentPath + "/" + node.name));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const filePath = currentPath + "/" + node.name;
|
|
39
|
+
//build route path from filePath
|
|
40
|
+
let route = filePath
|
|
41
|
+
.replace(/^.*(src\/api|api)/, "/api") //removed parent folder like src
|
|
42
|
+
.replace(/\.ts|js$/, "") //removes file extension
|
|
43
|
+
.replace(/\[(.+?)\]/g, ":$1") //converts [slug] to :slug
|
|
44
|
+
.replace("/index", "");
|
|
45
|
+
|
|
46
|
+
routes.push({ route, filePath: node.relative_path });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
files.forEach((file) => walk(file, ""));
|
|
50
|
+
return routes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const routes = buildApiRoutes(combinedFiles);
|
|
54
|
+
|
|
55
|
+
// --- match path to route ---
|
|
56
|
+
/** Matches the given path to API routes generated from the api folder + the aaex/api folder*/
|
|
57
|
+
function pathToRoute(pathname) {
|
|
58
|
+
for (const r of routes) {
|
|
59
|
+
const matcher = match(r.route, { decode: decodeURIComponent });
|
|
60
|
+
const matched = matcher(pathname);
|
|
61
|
+
if (matched) return { route: r, params: matched.params };
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- export ---
|
|
67
|
+
export { routes, pathToRoute };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { connectToDatabase } from "../../framework/database/mongodb";
|
|
2
|
+
import type { Request, Response } from "express";
|
|
3
|
+
import bcrypt from "bcrypt";
|
|
4
|
+
import jwt from "jsonwebtoken";
|
|
5
|
+
import { LoginUser } from "../../../src/models/User";
|
|
6
|
+
|
|
7
|
+
export const POST = async (req: Request, res: Response) => {
|
|
8
|
+
const { email, password }: LoginUser = req.body;
|
|
9
|
+
|
|
10
|
+
if (!email || !password) {
|
|
11
|
+
return res.status(400).json({ error: "Missing fields!" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { db } = await connectToDatabase();
|
|
15
|
+
|
|
16
|
+
if (!process.env.JWT_SECRET) {
|
|
17
|
+
console.error("Missing: JWT_SECRET from environment variables");
|
|
18
|
+
return res
|
|
19
|
+
.status(500)
|
|
20
|
+
.json({ error: "Internal server error! Try again later" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
24
|
+
const user = await db.collection("users").findOne({ email: normalizedEmail });
|
|
25
|
+
|
|
26
|
+
if (!user) {
|
|
27
|
+
return res.status(400).json({ error: "Invalid email or password" });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const compared = await bcrypt.compare(password, user.password);
|
|
31
|
+
|
|
32
|
+
if (!compared) {
|
|
33
|
+
return res.status(400).json({ error: "Invalid email or password" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const expiration = process.env.JWT_EXP ?? "24h";
|
|
37
|
+
|
|
38
|
+
const token = jwt.sign(
|
|
39
|
+
{
|
|
40
|
+
id: user._id.toString(),
|
|
41
|
+
email: user.email,
|
|
42
|
+
username: user.username,
|
|
43
|
+
},
|
|
44
|
+
process.env.JWT_SECRET as string,
|
|
45
|
+
{ expiresIn: expiration as any } //fixes stupid thing where it wont accept the sring variable because its not number | ms.stringvlue | undefined
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return res.status(200).json({
|
|
49
|
+
ok: true,
|
|
50
|
+
user: {
|
|
51
|
+
id: user._id.toString(),
|
|
52
|
+
name: user.username,
|
|
53
|
+
email: user.email,
|
|
54
|
+
},
|
|
55
|
+
token,
|
|
56
|
+
});
|
|
57
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Request, Response } from "express";
|
|
2
|
+
import bcrypt from "bcrypt";
|
|
3
|
+
import { connectToDatabase } from "../../framework/database/mongodb";
|
|
4
|
+
import { CreateUser } from "../../../src/models/User";
|
|
5
|
+
export const POST = async (req: Request, res: Response) => {
|
|
6
|
+
|
|
7
|
+
const { email, username, password, confirmPass }: CreateUser = req.body;
|
|
8
|
+
|
|
9
|
+
if (!username || !email || !password || !confirmPass)
|
|
10
|
+
return res.status(400).json({ error: "Missing fields" });
|
|
11
|
+
|
|
12
|
+
if (password !== confirmPass)
|
|
13
|
+
return res.status(400).json({ error: "Passwords do not match" });
|
|
14
|
+
|
|
15
|
+
const { db } = await connectToDatabase();
|
|
16
|
+
|
|
17
|
+
const exists = await db.collection("users").findOne({ email });
|
|
18
|
+
if (exists)
|
|
19
|
+
return res
|
|
20
|
+
.status(409)
|
|
21
|
+
.json({ error: "User with that email already exists" });
|
|
22
|
+
|
|
23
|
+
const salt = 10;
|
|
24
|
+
const hashed = await bcrypt.hash(password, salt);
|
|
25
|
+
|
|
26
|
+
await db.collection("users").insertOne({
|
|
27
|
+
username,
|
|
28
|
+
email,
|
|
29
|
+
password: hashed,
|
|
30
|
+
createdAt: new Date(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return res.status(201).json({ ok: true });
|
|
34
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import type { Request, Response } from "express";
|
|
3
|
+
|
|
4
|
+
export async function POST(req: Request, res: Response) {
|
|
5
|
+
const { token } = req.body;
|
|
6
|
+
|
|
7
|
+
if (!process.env.JWT_SECRET) {
|
|
8
|
+
console.error("Missing: JWT_SECRET from environment");
|
|
9
|
+
return res.status(500).json({error: "Internal server error!"});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
14
|
+
return res.status(200).json({ valid: true, user: decoded });
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return res.status(401).json({ valid: false });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MongoClient, Db } from "mongodb";
|
|
2
|
+
|
|
3
|
+
const MONGO_URI = process.env.MONGO_URI || "mongodb://localhost:27017";
|
|
4
|
+
const DB_NAME = process.env.DB_NAME || "mydatabase";
|
|
5
|
+
|
|
6
|
+
if (!MONGO_URI) {
|
|
7
|
+
throw new Error("Please define the MONGO_URI environment variable inside .env");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let cachedClient: MongoClient | null = null;
|
|
11
|
+
let cachedDb: Db | null = null;
|
|
12
|
+
|
|
13
|
+
export async function connectToDatabase(): Promise<{ client: MongoClient; db: Db }> {
|
|
14
|
+
// Return cached connection if it exists
|
|
15
|
+
if (cachedClient && cachedDb) {
|
|
16
|
+
return { client: cachedClient, db: cachedDb };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const client = new MongoClient(MONGO_URI);
|
|
20
|
+
await client.connect();
|
|
21
|
+
const db = client.db(DB_NAME);
|
|
22
|
+
|
|
23
|
+
cachedClient = client;
|
|
24
|
+
cachedDb = db;
|
|
25
|
+
|
|
26
|
+
console.log("MongoDB connected:", DB_NAME);
|
|
27
|
+
|
|
28
|
+
return { client, db };
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "../../src/index.css";
|
|
2
|
+
import { StrictMode } from "react";
|
|
3
|
+
import { hydrateRoot } from "react-dom/client";
|
|
4
|
+
import App from "../../src/App";
|
|
5
|
+
import { BrowserRouter } from "react-router";
|
|
6
|
+
|
|
7
|
+
const initialData = (window as any).__INITIAL_DATA__;
|
|
8
|
+
|
|
9
|
+
hydrateRoot(
|
|
10
|
+
document.getElementById("root") as HTMLElement,
|
|
11
|
+
<StrictMode>
|
|
12
|
+
<BrowserRouter>
|
|
13
|
+
<App initialData={initialData} />
|
|
14
|
+
</BrowserRouter>
|
|
15
|
+
</StrictMode>
|
|
16
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
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, initialData= {}) {
|
|
7
|
+
const url = `${_url}`;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const html = renderToString(
|
|
11
|
+
<StrictMode>
|
|
12
|
+
<StaticRouter location={url}>
|
|
13
|
+
<App initialData={initialData} />
|
|
14
|
+
</StaticRouter>
|
|
15
|
+
</StrictMode>
|
|
16
|
+
);
|
|
17
|
+
return { html };
|
|
18
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export default function routeMatcher(routes, url) {
|
|
2
|
+
const segments = url.split("/").filter(Boolean); // "test/hej" → ["test", "hej"]
|
|
3
|
+
|
|
4
|
+
return matchLevel(routes, segments);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function matchLevel(routes, segments) {
|
|
8
|
+
for (const route of routes) {
|
|
9
|
+
const result = matchRoute(route, segments);
|
|
10
|
+
|
|
11
|
+
if (result) return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function matchRoute(route, segments) {
|
|
18
|
+
const isParam = /^:[a-zA-Z0-9_]+$/;
|
|
19
|
+
|
|
20
|
+
const [current, ...rest] = segments;
|
|
21
|
+
|
|
22
|
+
// Root index route
|
|
23
|
+
if (route.path === "" && segments.length === 0) {
|
|
24
|
+
return { route, params: {} };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Static match
|
|
28
|
+
if (route.path === current) {
|
|
29
|
+
if (rest.length === 0) {
|
|
30
|
+
return { route, params: {} };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Has children → go deeper
|
|
34
|
+
if (route.children) {
|
|
35
|
+
return matchLevel(route.children, rest);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Dynamic match → :slug , :id osv
|
|
40
|
+
if (isParam.test(route.path)) {
|
|
41
|
+
const paramName = route.path.slice(1);
|
|
42
|
+
|
|
43
|
+
if (rest.length === 0) {
|
|
44
|
+
return { route, params: { [paramName]: current } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Has children → go deeper
|
|
48
|
+
if (route.children) {
|
|
49
|
+
const matchedChild = matchLevel(route.children, rest);
|
|
50
|
+
if (matchedChild) {
|
|
51
|
+
return {
|
|
52
|
+
route: matchedChild.route,
|
|
53
|
+
params: { [paramName]: current, ...matchedChild.params },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
import dotenv from "dotenv";
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
// server.js is now in .aaex/server/
|
|
9
|
+
const projectRoot = path.resolve("."); // root of the project
|
|
10
|
+
let serverRoutes;
|
|
11
|
+
// Import BuildApiRoutes
|
|
12
|
+
import * as BuildApiRoutes from "../BuildApiRoutes.js";
|
|
13
|
+
import routeMatcher from "../matchServerRoutes.js";
|
|
14
|
+
|
|
15
|
+
const apiRoutes = BuildApiRoutes.default; // default export
|
|
16
|
+
const PathToRoute = BuildApiRoutes.pathToRoute; // named export
|
|
17
|
+
|
|
18
|
+
// Constants
|
|
19
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
20
|
+
const port = process.env.PORT || 5173;
|
|
21
|
+
const base = process.env.BASE || "/";
|
|
22
|
+
|
|
23
|
+
// Cached production HTML
|
|
24
|
+
const templateHtml = isProduction
|
|
25
|
+
? await fs.readFile(path.join(projectRoot, "dist/client/index.html"), "utf-8")
|
|
26
|
+
: "";
|
|
27
|
+
|
|
28
|
+
// Create HTTP server
|
|
29
|
+
const app = express();
|
|
30
|
+
|
|
31
|
+
/** @type {import('vite').ViteDevServer | undefined} */
|
|
32
|
+
let vite;
|
|
33
|
+
if (!isProduction) {
|
|
34
|
+
const { createServer } = await import("vite");
|
|
35
|
+
vite = await createServer({
|
|
36
|
+
server: { middlewareMode: true },
|
|
37
|
+
appType: "custom",
|
|
38
|
+
root: projectRoot,
|
|
39
|
+
base,
|
|
40
|
+
});
|
|
41
|
+
serverRoutes = (await vite.ssrLoadModule("/src/server-routes.ts")).default;
|
|
42
|
+
app.use(vite.middlewares);
|
|
43
|
+
} else {
|
|
44
|
+
const compression = (await import("compression")).default;
|
|
45
|
+
const sirv = (await import("sirv")).default;
|
|
46
|
+
app.use(compression());
|
|
47
|
+
app.use(
|
|
48
|
+
base,
|
|
49
|
+
sirv(path.join(projectRoot, "dist/client"), { extensions: [] })
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const serverRoutesModule = await import(
|
|
53
|
+
pathToFileURL(path.join(projectRoot, "dist/src/server-routes.js")).href
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
serverRoutes = serverRoutesModule.default;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// parse JSON bodies
|
|
60
|
+
app.use(express.json());
|
|
61
|
+
|
|
62
|
+
// parse URL-encoded bodies (optional, for form POSTs)
|
|
63
|
+
app.use(express.urlencoded({ extended: true }));
|
|
64
|
+
|
|
65
|
+
// API routing
|
|
66
|
+
app.use("/api", async (req, res) => {
|
|
67
|
+
const routeMatch = PathToRoute(req.path, apiRoutes);
|
|
68
|
+
|
|
69
|
+
if (!routeMatch)
|
|
70
|
+
return res.status(404).json({ error: "API route not found" });
|
|
71
|
+
|
|
72
|
+
const { route, params } = routeMatch;
|
|
73
|
+
let modulePath;
|
|
74
|
+
|
|
75
|
+
if (!isProduction) {
|
|
76
|
+
// DEV: Vite handles TS/JS loading
|
|
77
|
+
if (route.filePath.split("/")[0] == ".aaex") {
|
|
78
|
+
modulePath = `${route.filePath.replace(/^src\/api/, "")}`;
|
|
79
|
+
} else {
|
|
80
|
+
modulePath = `/src/api${route.filePath.replace(/^src\/api/, "")}`;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
// PROD: bundled JS
|
|
84
|
+
modulePath = pathToFileURL(
|
|
85
|
+
`./dist/server/api${route.filePath
|
|
86
|
+
.replace(/^src\/api/, "")
|
|
87
|
+
.replace(/\.ts$/, ".js")}`
|
|
88
|
+
).href;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const mod = !isProduction
|
|
93
|
+
? await vite.ssrLoadModule(modulePath)
|
|
94
|
+
: await import(modulePath);
|
|
95
|
+
|
|
96
|
+
const handler = mod[req.method]; // GET, POST, etc.
|
|
97
|
+
|
|
98
|
+
if (typeof handler !== "function") {
|
|
99
|
+
return res.status(405).json({ error: "Method not allowed" });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const result = await handler(req, res, params);
|
|
103
|
+
|
|
104
|
+
if (!res.headersSent) res.json(result);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error("API load error:", err);
|
|
107
|
+
res.status(500).json({ error: "Internal Server Error" });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// SSR HTML
|
|
112
|
+
app.use(/.*/, async (req, res) => {
|
|
113
|
+
try {
|
|
114
|
+
let url = req.originalUrl;
|
|
115
|
+
|
|
116
|
+
const routeMatch = routeMatcher(serverRoutes, url);
|
|
117
|
+
|
|
118
|
+
if (!routeMatch) {
|
|
119
|
+
return res.status(404).send("Not found");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Dynamicly import the file
|
|
123
|
+
const mod = await vite.ssrLoadModule(routeMatch.route.modulePath);
|
|
124
|
+
|
|
125
|
+
// Call load if it exist
|
|
126
|
+
let initialData = {};
|
|
127
|
+
if (mod.load) {
|
|
128
|
+
initialData = await mod.load(routeMatch.params);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let template;
|
|
132
|
+
let render;
|
|
133
|
+
if (!isProduction) {
|
|
134
|
+
template = await fs.readFile(
|
|
135
|
+
path.join(projectRoot, "index.html"),
|
|
136
|
+
"utf-8"
|
|
137
|
+
);
|
|
138
|
+
template = await vite.transformIndexHtml(url, template);
|
|
139
|
+
render = (await vite.ssrLoadModule("/.aaex/framework/entry-server.tsx"))
|
|
140
|
+
.render;
|
|
141
|
+
} else {
|
|
142
|
+
template = templateHtml;
|
|
143
|
+
render = (
|
|
144
|
+
await import(path.join(projectRoot, "dist/server/entry-server.js"))
|
|
145
|
+
).render;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const rendered = await render(url, initialData);
|
|
149
|
+
|
|
150
|
+
function XSSPrevention(unsafeString) {
|
|
151
|
+
return unsafeString.replace(/</g, "//u003c");
|
|
152
|
+
// .replace(/>/g, ">")
|
|
153
|
+
// .replace(/'/g, "'")
|
|
154
|
+
// .replace(/"/g, """);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const serializedData = JSON.stringify(initialData);
|
|
158
|
+
const safeData = XSSPrevention(serializedData);
|
|
159
|
+
|
|
160
|
+
const html = template
|
|
161
|
+
.replace("<!--app-head-->", rendered.head ?? "")
|
|
162
|
+
.replace("<!--app-html-->", rendered.html ?? "")
|
|
163
|
+
.replace(
|
|
164
|
+
"<!--initial-data-->",
|
|
165
|
+
`<script>window.__INITIAL_DATA__ = ${safeData}</script>`
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
res.status(200).set({ "Content-Type": "text/html" }).send(html);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
vite?.ssrFixStacktrace?.(e);
|
|
171
|
+
console.error(e);
|
|
172
|
+
res.status(500).send(e.stack);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Start server
|
|
177
|
+
app.listen(port, () => {
|
|
178
|
+
console.log(`Server started at http://localhost:${port}`);
|
|
179
|
+
});
|