add build serve and start on other things
All checks were successful
Code quality checks / biome (push) Successful in 7s

This commit is contained in:
creations 2025-05-22 18:44:56 -04:00
parent d1b1d0aeb5
commit 2552d305da
Signed by: creations
GPG key ID: 8F553AA4320FC711
22 changed files with 281 additions and 74 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ bun.lock
.env
/uploads
.idea
dist

View file

@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": true,
"ignore": []
"ignore": ["dist"]
},
"formatter": {
"enabled": true,

View file

@ -7,6 +7,7 @@ const environment: Environment = {
development:
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
fqdn: normalizeFqdn(process.env.FQDN) || "http://localhost:8080",
backendUrl: normalizeFqdn(process.env.BACKEND_URL) || "http://localhost:8080",
};
function verifyRequiredVariables(): void {

View file

@ -5,10 +5,9 @@
"private": true,
"type": "module",
"scripts": {
"start": "bun src/index.ts",
"start": "bun run build && bun src/serve.ts",
"dev": "bun src/index.ts --dev",
"build": "vite build",
"serve": "vite preview",
"build": "bun run src/build.ts",
"lint": "bunx biome check",
"lint:fix": "bunx biome check --fix",
"cleanup": "rm -rf logs node_modules bun.lock"
@ -23,6 +22,7 @@
},
"dependencies": {
"@creations.works/logger": "^1.0.3",
"@solidjs/router": "^0.15.3",
"solid-js": "^1.9.5"
}
}

33
src/build.ts Normal file
View 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.");

View file

@ -7,6 +7,9 @@
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/assets/favicon.ico" />
<title>Solid App</title>
<meta name="env-fqdn" content="__FQDN__" />
<meta name="env-backend-url" content="__BACKEND_URL__" />
</head>
<body>

View file

@ -1,16 +1,27 @@
import { resolve } from "node:path";
import { environment, verifyRequiredVariables } from "@config";
import { logger } from "@creations.works/logger";
import { createServer } from "vite";
import { type PluginOption, createServer } 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);
},
};
}
const server = await createServer({
root: resolve("src"),
publicDir: resolve("src/public"),
plugins: [solidPlugin(), tsconfigPaths()],
plugins: [solidPlugin(), tsconfigPaths(), injectEnvPlugin()],
server: {
port: environment.port,
host: environment.host,

View file

@ -1,5 +1,6 @@
export function normalizeFqdn(value?: string): string | null {
if (!value) return null;
if (!/^https?:\/\//.test(value)) return `https://${value}`;
return value;
const trimmed = value.replace(/\/+$/, "");
if (!/^https?:\/\//.test(trimmed)) return `https://${trimmed}`;
return trimmed;
}

12
src/lib/envClient.ts Normal file
View 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
View 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}`);

View file

@ -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
View 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 };

View file

@ -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
View 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);
}

View file

@ -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;
}

View 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);
}

View file

@ -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 "@views/css/index.css";
import App from "@views/App";
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.dataset.theme = prefersDark ? "dark" : "light";
const root = document.getElementById("root");
if (!(root instanceof HTMLElement)) throw new Error("Root element not found");
if (!(root instanceof HTMLElement)) {
throw new Error("Root element not found");
}
render(() => <App />, root);
render(
() => (
<Router>
<App />
</Router>
),
root,
);

12
src/views/pages/about.tsx Normal file
View 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
View 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
View 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
View 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
View file

@ -3,4 +3,5 @@ type Environment = {
host: string;
development: boolean;
fqdn: string;
backendUrl: string;
};