feat: upgrade

This commit is contained in:
Wlad Meixner 2025-07-26 16:43:54 +02:00
parent 5192a6c1e9
commit 36dec19875
No known key found for this signature in database
13 changed files with 3756 additions and 33 deletions

51
Caddyfile Normal file
View File

@ -0,0 +1,51 @@
:80 {
root * /srv
# Enable gzip compression
encode gzip
# SPA fallback - serve index.html for all routes that don't match files
try_files {path}.html
# Serve files
file_server
# Security headers
header {
# Disable FLoC tracking
Permissions-Policy interest-cohort=()
# Enable HSTS
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Prevent clickjacking
X-Frame-Options "DENY"
# Prevent MIME type sniffing
X-Content-Type-Options "nosniff"
# XSS protection
X-XSS-Protection "1; mode=block"
# Referrer policy
Referrer-Policy "strict-origin-when-cross-origin"
}
# Cache static assets
@static {
path *.js *.css *.ico *.jpg *.jpeg *.png *.svg *.gif *.woff *.woff2
}
header @static {
Cache-Control "public, max-age=31536000, immutable"
}
# Don't cache HTML files
@html {
path *.html
}
header @html {
Cache-Control "no-cache, no-store, must-revalidate"
Pragma "no-cache"
Expires "0"
}
}

32
Dockerfile Normal file
View File

@ -0,0 +1,32 @@
# Build stage
FROM oven/bun:1 AS builder
WORKDIR /app
# Copy package files
COPY package.json bun.lock ./
# Install dependencies
RUN bun install --frozen-lockfile
# Copy source code
COPY . .
# Build the application
RUN --mount=type=cache,target=/app/node_modules/.cache/imagetools bun run build
# Production stage
FROM caddy:2-alpine
# Copy built static files from builder stage
COPY --from=builder /app/build /srv
# Copy Caddyfile
COPY Caddyfile /etc/caddy/Caddyfile
# Expose port 80
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1

3532
bun.lock Normal file

File diff suppressed because it is too large Load Diff

22
index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/icon-48x48.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Magento Destroyes parody game" />
<link rel="apple-touch-icon" href="/icon-192x192.png" />
<link
href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap"
rel="stylesheet"
/>
<link rel="manifest" href="/manifest.json" />
<title>Magento Destroyers</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

33
index.ts Normal file
View File

@ -0,0 +1,33 @@
// Following CLAUDE.md instructions for serving HTML with Bun
import indexHtml from "./build/index.html";
Bun.serve({
port: 3001,
routes: {
"/": indexHtml,
// Serve static files
"/*": async (req) => {
const url = new URL(req.url);
const path = url.pathname;
try {
// Try to serve file from build directory
const file = Bun.file(`./build${path}`);
if (await file.exists()) {
return new Response(file);
}
} catch (e) {
// File not found
}
// Fallback to index.html for client-side routing
return new Response(Bun.file("./build/index.html"));
}
},
development: {
hmr: false, // Disable HMR for production build
console: true,
}
});
console.log("Server running at http://localhost:3001");

View File

@ -3,27 +3,33 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^14.6.1",
"@types/jest": "^24.0.0", "@types/jest": "^30.0.0",
"@types/node": "^12.0.0", "@types/node": "^24.1.0",
"@types/react": "^16.9.0", "@types/react": "^19.1.8",
"@types/react-dom": "^16.9.0", "@types/react-dom": "^19.1.6",
"@types/styled-components": "^5.1.2", "@types/styled-components": "^5.1.34",
"react": "^16.13.1", "react": "^19.1.0",
"react-dom": "^16.13.1", "react-dom": "^19.1.0",
"react-scripts": "3.4.1", "react-scripts": "5.0.1",
"styled-components": "^5.1.1", "styled-components": "^6.1.19",
"styled-icons": "^10.22.0", "styled-icons": "^10.47.1",
"three": "^0.121.1", "three": "^0.178.0",
"typescript": "~3.7.2" "typescript": "~5.8.3"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "dev": "vite",
"build": "react-scripts build", "build": "tsc && vite build",
"test": "react-scripts test", "preview": "vite preview",
"eject": "react-scripts eject" "start": "vite",
"test": "bun test",
"serve": "bun server.ts",
"old:start": "react-scripts start",
"old:build": "react-scripts build",
"old:test": "react-scripts test",
"old:eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
@ -39,5 +45,19 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"module": "src/index.tsx",
"type": "module",
"devDependencies": {
"@types/bun": "^1.2.19",
"@types/minimatch": "^6.0.0",
"@types/three": "^0.178.1",
"@vitejs/plugin-react": "^4.7.0",
"vite": "^7.0.6",
"vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^5.1.4"
},
"peerDependencies": {
"typescript": "^5"
} }
} }

View File

@ -104,6 +104,7 @@ const ComputerMonitor: React.FC<{
playing: boolean; playing: boolean;
embed?: boolean; embed?: boolean;
onPowerClick?: () => void; onPowerClick?: () => void;
children?: React.ReactNode;
}> = ({ children, playing, onPowerClick, embed }) => { }> = ({ children, playing, onPowerClick, embed }) => {
return ( return (
<StyledMonitorContainer embed={embed}> <StyledMonitorContainer embed={embed}>

View File

@ -48,8 +48,8 @@ const createGameInstance = (target: HTMLElement) => {
}; };
export default (props: CanvasProps) => { export default (props: CanvasProps) => {
const container = useRef<HTMLCanvasElement | undefined>(); const container = useRef<HTMLCanvasElement | undefined>(undefined);
const game = useRef<Game | undefined>(); const game = useRef<Game | undefined>(undefined);
useEffect(() => { useEffect(() => {
if (!props.gameOver && props.started) { if (!props.gameOver && props.started) {

View File

@ -223,8 +223,7 @@ class Game {
points.push(new THREE.Vector3(-1000, 0, 0)); points.push(new THREE.Vector3(-1000, 0, 0));
points.push(new THREE.Vector3(1000, 0, 0)); points.push(new THREE.Vector3(1000, 0, 0));
const geometry = new THREE.Geometry(); const geometry = new THREE.BufferGeometry().setFromPoints(points);
geometry.vertices = points;
const line = new THREE.Line(geometry, material); const line = new THREE.Line(geometry, material);
line.computeLineDistances(); line.computeLineDistances();

View File

@ -12,7 +12,7 @@ export const createProjectile = (scene: THREE.Scene, origin: THREE.Vector3) => {
new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0, 0, -1) new THREE.Vector3(0, 0, -1)
); );
let baseGeometry = new THREE.TubeBufferGeometry(curve, 25, 1, 8, false); let baseGeometry = new THREE.TubeGeometry(curve, 25, 1, 8, false);
let material = new THREE.MeshBasicMaterial({ color: 0x545454 }); let material = new THREE.MeshBasicMaterial({ color: 0x545454 });
const projectile = new THREE.Mesh(baseGeometry, material); const projectile = new THREE.Mesh(baseGeometry, material);
@ -21,7 +21,7 @@ export const createProjectile = (scene: THREE.Scene, origin: THREE.Vector3) => {
return projectile; return projectile;
}; };
const materialShader: THREE.Shader = { const materialShader = {
uniforms: { uniforms: {
uColor: new THREE.Uniform(new THREE.Color("0xdddddd")), uColor: new THREE.Uniform(new THREE.Color("0xdddddd")),
}, },

View File

@ -1,15 +1,18 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import './index.css'; import './index.css';
import App from './App'; import App from './App';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
ReactDOM.render( const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>
document.getElementById('root')
); );
}
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.

View File

@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom';

30
vite.config.ts Normal file
View File

@ -0,0 +1,30 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import svgr from 'vite-plugin-svgr';
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
tsconfigPaths(),
svgr({
svgrOptions: {
exportType: 'named',
},
}),
],
server: {
port: 3000,
open: true,
},
build: {
outDir: 'build',
sourcemap: true,
},
resolve: {
alias: {
'@': '/src',
},
},
});