add build serve and start on other things
All checks were successful
Code quality checks / biome (push) Successful in 7s
All checks were successful
Code quality checks / biome (push) Successful in 7s
This commit is contained in:
parent
d1b1d0aeb5
commit
2552d305da
22 changed files with 281 additions and 74 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ bun.lock
|
||||||
.env
|
.env
|
||||||
/uploads
|
/uploads
|
||||||
.idea
|
.idea
|
||||||
|
dist
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": true,
|
"ignoreUnknown": true,
|
||||||
"ignore": []
|
"ignore": ["dist"]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
|
@ -7,6 +7,7 @@ const environment: Environment = {
|
||||||
development:
|
development:
|
||||||
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
|
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
|
||||||
fqdn: normalizeFqdn(process.env.FQDN) || "http://localhost:8080",
|
fqdn: normalizeFqdn(process.env.FQDN) || "http://localhost:8080",
|
||||||
|
backendUrl: normalizeFqdn(process.env.BACKEND_URL) || "http://localhost:8080",
|
||||||
};
|
};
|
||||||
|
|
||||||
function verifyRequiredVariables(): void {
|
function verifyRequiredVariables(): void {
|
||||||
|
|
|
@ -5,10 +5,9 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun src/index.ts",
|
"start": "bun run build && bun src/serve.ts",
|
||||||
"dev": "bun src/index.ts --dev",
|
"dev": "bun src/index.ts --dev",
|
||||||
"build": "vite build",
|
"build": "bun run src/build.ts",
|
||||||
"serve": "vite preview",
|
|
||||||
"lint": "bunx biome check",
|
"lint": "bunx biome check",
|
||||||
"lint:fix": "bunx biome check --fix",
|
"lint:fix": "bunx biome check --fix",
|
||||||
"cleanup": "rm -rf logs node_modules bun.lock"
|
"cleanup": "rm -rf logs node_modules bun.lock"
|
||||||
|
@ -23,6 +22,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@creations.works/logger": "^1.0.3",
|
"@creations.works/logger": "^1.0.3",
|
||||||
|
"@solidjs/router": "^0.15.3",
|
||||||
"solid-js": "^1.9.5"
|
"solid-js": "^1.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
src/build.ts
Normal file
33
src/build.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { environment, verifyRequiredVariables } from "@config";
|
||||||
|
import { logger } from "@creations.works/logger";
|
||||||
|
import { build } from "vite";
|
||||||
|
import type { PluginOption } from "vite";
|
||||||
|
import solidPlugin from "vite-plugin-solid";
|
||||||
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
|
||||||
|
verifyRequiredVariables();
|
||||||
|
|
||||||
|
function injectEnvPlugin(): PluginOption {
|
||||||
|
return {
|
||||||
|
name: "inject-env",
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
return html
|
||||||
|
.replace("__FQDN__", environment.fqdn)
|
||||||
|
.replace("__BACKEND_URL__", environment.backendUrl);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await build({
|
||||||
|
root: resolve("src"),
|
||||||
|
publicDir: resolve("src/public"),
|
||||||
|
plugins: [solidPlugin(), tsconfigPaths(), injectEnvPlugin()],
|
||||||
|
build: {
|
||||||
|
outDir: resolve("dist"),
|
||||||
|
emptyOutDir: true,
|
||||||
|
target: "esnext",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info("Production build complete.");
|
|
@ -7,6 +7,9 @@
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<link rel="shortcut icon" type="image/ico" href="/assets/favicon.ico" />
|
<link rel="shortcut icon" type="image/ico" href="/assets/favicon.ico" />
|
||||||
<title>Solid App</title>
|
<title>Solid App</title>
|
||||||
|
|
||||||
|
<meta name="env-fqdn" content="__FQDN__" />
|
||||||
|
<meta name="env-backend-url" content="__BACKEND_URL__" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
15
src/index.ts
15
src/index.ts
|
@ -1,16 +1,27 @@
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { environment, verifyRequiredVariables } from "@config";
|
import { environment, verifyRequiredVariables } from "@config";
|
||||||
import { logger } from "@creations.works/logger";
|
import { logger } from "@creations.works/logger";
|
||||||
import { createServer } from "vite";
|
import { type PluginOption, createServer } from "vite";
|
||||||
import solidPlugin from "vite-plugin-solid";
|
import solidPlugin from "vite-plugin-solid";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
|
||||||
verifyRequiredVariables();
|
verifyRequiredVariables();
|
||||||
|
|
||||||
|
function injectEnvPlugin(): PluginOption {
|
||||||
|
return {
|
||||||
|
name: "inject-env",
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
return html
|
||||||
|
.replace("__FQDN__", environment.fqdn)
|
||||||
|
.replace("__BACKEND_URL__", environment.backendUrl);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const server = await createServer({
|
const server = await createServer({
|
||||||
root: resolve("src"),
|
root: resolve("src"),
|
||||||
publicDir: resolve("src/public"),
|
publicDir: resolve("src/public"),
|
||||||
plugins: [solidPlugin(), tsconfigPaths()],
|
plugins: [solidPlugin(), tsconfigPaths(), injectEnvPlugin()],
|
||||||
server: {
|
server: {
|
||||||
port: environment.port,
|
port: environment.port,
|
||||||
host: environment.host,
|
host: environment.host,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export function normalizeFqdn(value?: string): string | null {
|
export function normalizeFqdn(value?: string): string | null {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
if (!/^https?:\/\//.test(value)) return `https://${value}`;
|
const trimmed = value.replace(/\/+$/, "");
|
||||||
return value;
|
if (!/^https?:\/\//.test(trimmed)) return `https://${trimmed}`;
|
||||||
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|
12
src/lib/envClient.ts
Normal file
12
src/lib/envClient.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export function getClientEnv() {
|
||||||
|
const fqdn =
|
||||||
|
document.querySelector('meta[name="env-fqdn"]')?.getAttribute("content") ||
|
||||||
|
"http://localhost:8080";
|
||||||
|
|
||||||
|
const backendUrl =
|
||||||
|
document
|
||||||
|
.querySelector('meta[name="env-backend-url"]')
|
||||||
|
?.getAttribute("content") || "http://localhost:8080";
|
||||||
|
|
||||||
|
return { fqdn, backendUrl };
|
||||||
|
}
|
30
src/serve.ts
Normal file
30
src/serve.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { environment } from "@config";
|
||||||
|
import { logger } from "@creations.works/logger";
|
||||||
|
import { file } from "bun";
|
||||||
|
|
||||||
|
const dist = resolve("dist");
|
||||||
|
|
||||||
|
Bun.serve({
|
||||||
|
port: environment.port,
|
||||||
|
hostname: environment.host,
|
||||||
|
async fetch(req) {
|
||||||
|
const url = new URL(req.url);
|
||||||
|
let pathname = decodeURIComponent(url.pathname);
|
||||||
|
if (pathname === "/") pathname = "/index.html";
|
||||||
|
|
||||||
|
const filePath = resolve(dist + pathname);
|
||||||
|
const bunFile = file(filePath);
|
||||||
|
|
||||||
|
if (await bunFile.exists()) {
|
||||||
|
return new Response(bunFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallback = file(resolve(dist, "index.html"));
|
||||||
|
return new Response(fallback, {
|
||||||
|
headers: { "Content-Type": "text/html" },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Server running at ${environment.fqdn}`);
|
|
@ -1,13 +0,0 @@
|
||||||
import type { Component } from "solid-js";
|
|
||||||
|
|
||||||
import styles from "@views/css/App.module.css";
|
|
||||||
|
|
||||||
const App: Component = () => {
|
|
||||||
return (
|
|
||||||
<div class={styles.App}>
|
|
||||||
<header class={styles.header} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
18
src/views/app.tsx
Normal file
18
src/views/app.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Route } from "@solidjs/router";
|
||||||
|
import { about } from "@views/pages/about";
|
||||||
|
import { error } from "@views/pages/error";
|
||||||
|
import { home } from "@views/pages/home";
|
||||||
|
import { login } from "@views/pages/login";
|
||||||
|
|
||||||
|
import type { Component } from "solid-js";
|
||||||
|
|
||||||
|
const app: Component = () => (
|
||||||
|
<>
|
||||||
|
<Route path="/" component={home} />
|
||||||
|
<Route path="/login" component={login} />
|
||||||
|
<Route path="/about" component={about} />
|
||||||
|
<Route path="*" component={error} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { app };
|
|
@ -1,33 +0,0 @@
|
||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
animation: logo-spin infinite 20s linear;
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
color: #b318f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
26
src/views/css/global.css
Normal file
26
src/views/css/global.css
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
:root {
|
||||||
|
--background: #f4f4f4;
|
||||||
|
--text: #000;
|
||||||
|
--input-background: #fff;
|
||||||
|
--input-border: #ccc;
|
||||||
|
--button-background: #4f46e5;
|
||||||
|
--button-hover-background: #4338ca;
|
||||||
|
--button-text: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] {
|
||||||
|
--background: #18181b;
|
||||||
|
--text: #f9fafb;
|
||||||
|
--input-background: #27272a;
|
||||||
|
--input-border: #3f3f46;
|
||||||
|
--button-background: #6366f1;
|
||||||
|
--button-hover-background: #4f46e5;
|
||||||
|
--button-text: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
|
||||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
|
||||||
monospace;
|
|
||||||
}
|
|
45
src/views/css/login.module.css
Normal file
45
src/views/css/login.module.css
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: var(--input-background);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
color: var(--button-text);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background-color: var(--button-hover-background);
|
||||||
|
}
|
|
@ -1,12 +1,20 @@
|
||||||
|
import "@views/css/global.css";
|
||||||
|
|
||||||
|
import { Router } from "@solidjs/router";
|
||||||
|
import { app as App } from "@views/app";
|
||||||
import { render } from "solid-js/web";
|
import { render } from "solid-js/web";
|
||||||
|
|
||||||
import "@views/css/index.css";
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
import App from "@views/App";
|
document.documentElement.dataset.theme = prefersDark ? "dark" : "light";
|
||||||
|
|
||||||
const root = document.getElementById("root");
|
const root = document.getElementById("root");
|
||||||
|
if (!(root instanceof HTMLElement)) throw new Error("Root element not found");
|
||||||
|
|
||||||
if (!(root instanceof HTMLElement)) {
|
render(
|
||||||
throw new Error("Root element not found");
|
() => (
|
||||||
}
|
<Router>
|
||||||
|
<App />
|
||||||
render(() => <App />, root);
|
</Router>
|
||||||
|
),
|
||||||
|
root,
|
||||||
|
);
|
||||||
|
|
12
src/views/pages/about.tsx
Normal file
12
src/views/pages/about.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import type { Component } from "solid-js";
|
||||||
|
|
||||||
|
const about: Component = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>About</h1>
|
||||||
|
<p>This is the about page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { about };
|
12
src/views/pages/error.tsx
Normal file
12
src/views/pages/error.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import type { Component } from "solid-js";
|
||||||
|
|
||||||
|
const error: Component = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>404 - Not Found</h1>
|
||||||
|
<p>The page you're looking for doesn't exist.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { error };
|
33
src/views/pages/home.tsx
Normal file
33
src/views/pages/home.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { getClientEnv } from "@lib/envClient";
|
||||||
|
import { useNavigate } from "@solidjs/router";
|
||||||
|
import { onMount } from "solid-js";
|
||||||
|
|
||||||
|
const home = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { backendUrl } = getClientEnv();
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${backendUrl}/auth/session`, {
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
if (data?.valid) {
|
||||||
|
navigate("/dashboard", { replace: true });
|
||||||
|
} else {
|
||||||
|
navigate("/login", { replace: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigate("/login", { replace: true });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
navigate("/login", { replace: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { home };
|
19
src/views/pages/login.tsx
Normal file
19
src/views/pages/login.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import styles from "@views/css/login.module.css";
|
||||||
|
import type { Component } from "solid-js";
|
||||||
|
|
||||||
|
const login: Component = () => {
|
||||||
|
return (
|
||||||
|
<div class={styles.container}>
|
||||||
|
<h1 class={styles.title}>Login</h1>
|
||||||
|
<form class={styles.form}>
|
||||||
|
<input type="text" placeholder="Username" class={styles.input} />
|
||||||
|
<input type="password" placeholder="Password" class={styles.input} />
|
||||||
|
<button type="submit" class={styles.button}>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { login };
|
1
types/config.d.ts
vendored
1
types/config.d.ts
vendored
|
@ -3,4 +3,5 @@ type Environment = {
|
||||||
host: string;
|
host: string;
|
||||||
development: boolean;
|
development: boolean;
|
||||||
fqdn: string;
|
fqdn: string;
|
||||||
|
backendUrl: string;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue