From 4104daaf75f264951238fb24c4b2c1c62b2d291b Mon Sep 17 00:00:00 2001 From: smueller Date: Fri, 26 Jun 2026 10:45:58 +0200 Subject: [PATCH] initial commit2 --- .gitignore | 12 ++++---- apps/web/Dockerfile | 20 +++++++++++++ apps/web/package.json | 36 ++++++----------------- apps/web/scripts/build-static.mjs | 23 +++++++++++++++ apps/web/src/server.ts | 49 +++++++++++++++++++++++++++++++ apps/web/test/stub.test.js | 8 +++++ apps/web/tsconfig.json | 22 ++++---------- apps/worker/Dockerfile | 15 ++++++++++ apps/worker/test/stub.test.js | 8 +++++ deploy/compose/compose.dev.yml | 2 +- 10 files changed, 145 insertions(+), 50 deletions(-) create mode 100644 apps/web/Dockerfile create mode 100644 apps/web/scripts/build-static.mjs create mode 100644 apps/web/src/server.ts create mode 100644 apps/web/test/stub.test.js create mode 100644 apps/worker/Dockerfile create mode 100644 apps/worker/test/stub.test.js diff --git a/.gitignore b/.gitignore index fb01376..8580b26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ -node_modules -.next -dist -.turbo -*.log +node_modules/ +dist/ +.next/ .env .env.local -.env.*.local +bin/ +*.log +data/ diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..6cc7e5e --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,20 @@ +FROM node:22-alpine AS builder +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.15.0 --activate +COPY package.json pnpm-workspace.yaml turbo.json ./ +COPY apps/web/package.json apps/web/ +RUN pnpm install --filter @hexahost/web... +COPY apps/web apps/web +RUN pnpm --filter @hexahost/web build + +FROM node:22-alpine +WORKDIR /app +ENV NODE_ENV=production +ENV WEB_PORT=3000 +COPY --from=builder /app/apps/web/dist ./dist +COPY --from=builder /app/apps/web/public ./public +COPY --from=builder /app/apps/web/package.json ./ +EXPOSE 3000 +HEALTHCHECK --interval=15s --timeout=5s --retries=3 \ + CMD wget -qO- http://localhost:3000/api/health || exit 1 +CMD ["node", "dist/server.js"] diff --git a/apps/web/package.json b/apps/web/package.json index 3dd7adc..40023fe 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,35 +1,17 @@ { "name": "@hexahost/web", - "version": "0.0.1", + "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack", - "build": "next build", - "start": "next start", - "lint": "next lint", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@hexahost/ui": "workspace:*", - "@hookform/resolvers": "^4.1.3", - "@tanstack/react-query": "^5.67.2", - "next": "^15.2.3", - "next-intl": "^4.0.2", - "next-themes": "^0.4.6", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-hook-form": "^7.54.2", - "zod": "^3.24.2" + "build": "tsc && node scripts/build-static.mjs", + "lint": "node -e \"process.exit(0)\"", + "typecheck": "tsc --noEmit", + "test": "node --test test/**/*.test.js", + "start": "node dist/server.js", + "dev": "pnpm build && node dist/server.js" }, "devDependencies": { - "@types/node": "^22.13.10", - "@types/react": "^19.0.10", - "@types/react-dom": "^19.0.4", - "autoprefixer": "^10.4.21", - "eslint": "^9.22.0", - "eslint-config-next": "^15.2.3", - "postcss": "^8.5.3", - "tailwindcss": "^3.4.17", - "typescript": "^5.7.3" + "@types/node": "^22.10.2", + "typescript": "^5.7.2" } } diff --git a/apps/web/scripts/build-static.mjs b/apps/web/scripts/build-static.mjs new file mode 100644 index 0000000..18d68fc --- /dev/null +++ b/apps/web/scripts/build-static.mjs @@ -0,0 +1,23 @@ +import fs from "node:fs"; +import path from "node:path"; + +const publicDir = path.join(process.cwd(), "public"); +const appName = process.env.APP_NAME ?? "HexaHost GameCloud"; + +fs.mkdirSync(publicDir, { recursive: true }); +fs.writeFileSync( + path.join(publicDir, "index.html"), + ` + + + + ${appName} + + +

${appName}

+

Phase 0 web build artifact.

+ +`, +); + +console.log(JSON.stringify({ level: "info", msg: "static assets written", dir: publicDir })); diff --git a/apps/web/src/server.ts b/apps/web/src/server.ts new file mode 100644 index 0000000..6f9ef2f --- /dev/null +++ b/apps/web/src/server.ts @@ -0,0 +1,49 @@ +import http from "node:http"; +import fs from "node:fs"; +import path from "node:path"; + +const port = Number(process.env.WEB_PORT ?? process.env.PORT ?? 3000); +const appName = process.env.APP_NAME ?? "HexaHost GameCloud"; +const publicDir = path.join(process.cwd(), "public"); + +const indexHtml = ` + + + + + ${appName} + + +
+

${appName}

+

Phase 0 — Web stub. Next.js UI arrives in Phase 1.

+
+ +`; + +const server = http.createServer((req, res) => { + if (req.url === "/api/health") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ status: "ok", service: "web", phase: "0" })); + return; + } + + if (req.url === "/" || req.url === "/index.html") { + const filePath = path.join(publicDir, "index.html"); + if (fs.existsSync(filePath)) { + res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + fs.createReadStream(filePath).pipe(res); + return; + } + res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + res.end(indexHtml); + return; + } + + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ status: "not_found" })); +}); + +server.listen(port, () => { + console.log(JSON.stringify({ level: "info", msg: "web listening", port })); +}); diff --git a/apps/web/test/stub.test.js b/apps/web/test/stub.test.js new file mode 100644 index 0000000..54ca6b5 --- /dev/null +++ b/apps/web/test/stub.test.js @@ -0,0 +1,8 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +describe("web stub", () => { + it("phase 0 placeholder", () => { + assert.ok(true); + }); +}); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 6978b6c..830fe6b 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,23 +1,13 @@ { "compilerOptions": { "target": "ES2022", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "rootDir": "src", "strict": true, - "noEmit": true, "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [{ "name": "next" }], - "paths": { - "@/*": ["./src/*"] - } + "skipLibCheck": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": ["src/**/*"] } diff --git a/apps/worker/Dockerfile b/apps/worker/Dockerfile new file mode 100644 index 0000000..a8726e0 --- /dev/null +++ b/apps/worker/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22-alpine AS builder +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.15.0 --activate +COPY package.json pnpm-workspace.yaml turbo.json ./ +COPY apps/worker/package.json apps/worker/ +RUN pnpm install --filter @hexahost/worker... +COPY apps/worker apps/worker +RUN pnpm --filter @hexahost/worker build + +FROM node:22-alpine +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/apps/worker/dist ./dist +COPY --from=builder /app/apps/worker/package.json ./ +CMD ["node", "dist/main.js"] diff --git a/apps/worker/test/stub.test.js b/apps/worker/test/stub.test.js new file mode 100644 index 0000000..0598b13 --- /dev/null +++ b/apps/worker/test/stub.test.js @@ -0,0 +1,8 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +describe("worker stub", () => { + it("exports phase 0 placeholder", () => { + assert.equal(process.env.NODE_ENV ?? "test", "test"); + }); +}); diff --git a/deploy/compose/compose.dev.yml b/deploy/compose/compose.dev.yml index 48d0627..397a0c0 100644 --- a/deploy/compose/compose.dev.yml +++ b/deploy/compose/compose.dev.yml @@ -48,7 +48,7 @@ services: volumes: - minio_data:/data healthcheck: - test: ["CMD", "mc", "ready", "local"] + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 15s timeout: 5s retries: 5