@@ -165,30 +165,194 @@ mkdir -p /var/www
165165cd /var/www
166166git clone --depth 1 https://github.com/stacksjs/stacks.git app
167167cd app
168+
169+ # Remove linked packages from ALL package.json files (they don't exist on the server)
170+ echo "Removing linked packages from all package.json files..."
171+ /root/.bun/bin/bun -e "
172+ import { Glob } from 'bun';
173+
174+ async function cleanPackageJson(filePath) {
175+ try {
176+ const pkg = await Bun.file(filePath).json();
177+ let modified = false;
178+
179+ // Remove link: dependencies
180+ for (const key of Object.keys(pkg.dependencies || {})) {
181+ if (typeof pkg.dependencies[key] === 'string' && pkg.dependencies[key].startsWith('link:')) {
182+ console.log('[' + filePath + '] Removing linked dependency: ' + key);
183+ delete pkg.dependencies[key];
184+ modified = true;
185+ }
186+ }
187+ for (const key of Object.keys(pkg.devDependencies || {})) {
188+ if (typeof pkg.devDependencies[key] === 'string' && pkg.devDependencies[key].startsWith('link:')) {
189+ console.log('[' + filePath + '] Removing linked devDependency: ' + key);
190+ delete pkg.devDependencies[key];
191+ modified = true;
192+ }
193+ }
194+ // Also handle workspace: dependencies
195+ for (const key of Object.keys(pkg.dependencies || {})) {
196+ if (typeof pkg.dependencies[key] === 'string' && pkg.dependencies[key].startsWith('workspace:')) {
197+ console.log('[' + filePath + '] Removing workspace dependency: ' + key);
198+ delete pkg.dependencies[key];
199+ modified = true;
200+ }
201+ }
202+ for (const key of Object.keys(pkg.devDependencies || {})) {
203+ if (typeof pkg.devDependencies[key] === 'string' && pkg.devDependencies[key].startsWith('workspace:')) {
204+ console.log('[' + filePath + '] Removing workspace devDependency: ' + key);
205+ delete pkg.devDependencies[key];
206+ modified = true;
207+ }
208+ }
209+
210+ if (modified) {
211+ await Bun.write(filePath, JSON.stringify(pkg, null, 2));
212+ console.log('[' + filePath + '] Cleaned');
213+ }
214+ } catch (e) {
215+ console.log('[' + filePath + '] Error: ' + e.message);
216+ }
217+ }
218+
219+ // Find and clean all package.json files
220+ const glob = new Glob('**/package.json');
221+ for await (const file of glob.scan({ cwd: '.', absolute: true })) {
222+ // Skip node_modules
223+ if (file.includes('node_modules')) continue;
224+ await cleanPackageJson(file);
225+ }
226+ console.log('All package.json files cleaned for production install');
227+ "
228+
229+ # Remove bun.lock to avoid lockfile conflicts after modifying package.json
230+ rm -f bun.lock
231+
232+ # Install dependencies
233+ echo "Installing dependencies..."
234+ /root/.bun/bin/bun install --no-save || echo "Install completed with warnings"
235+
236+ echo "Building assets..."
237+ /root/.bun/bin/bun run build || echo "Build step skipped (no build script or failed)"
238+
239+ # Create public/dist directory for assets if it doesn't exist
240+ mkdir -p public/dist
241+ mkdir -p storage/public/assets
242+
168243cat > .env << 'ENVEOF'
169244APP_ENV=production
170245APP_URL=https://\${DomainName}
171246PORT=80
172247DEBUG=false
173248ENVEOF
174249cat > server-prod.ts << 'SERVEREOF'
250+ // MIME type mapping for static assets
251+ const mimeTypes: Record<string, string> = {
252+ '.html': 'text/html; charset=utf-8',
253+ '.htm': 'text/html; charset=utf-8',
254+ '.css': 'text/css; charset=utf-8',
255+ '.js': 'application/javascript; charset=utf-8',
256+ '.mjs': 'application/javascript; charset=utf-8',
257+ '.json': 'application/json; charset=utf-8',
258+ '.xml': 'application/xml; charset=utf-8',
259+ '.png': 'image/png',
260+ '.jpg': 'image/jpeg',
261+ '.jpeg': 'image/jpeg',
262+ '.gif': 'image/gif',
263+ '.svg': 'image/svg+xml',
264+ '.ico': 'image/x-icon',
265+ '.webp': 'image/webp',
266+ '.avif': 'image/avif',
267+ '.woff': 'font/woff',
268+ '.woff2': 'font/woff2',
269+ '.ttf': 'font/ttf',
270+ '.otf': 'font/otf',
271+ '.eot': 'application/vnd.ms-fontobject',
272+ '.pdf': 'application/pdf',
273+ '.txt': 'text/plain; charset=utf-8',
274+ '.mp3': 'audio/mpeg',
275+ '.mp4': 'video/mp4',
276+ '.webm': 'video/webm',
277+ '.ogg': 'audio/ogg',
278+ '.wav': 'audio/wav',
279+ '.wasm': 'application/wasm',
280+ '.map': 'application/json',
281+ }
282+
283+ function getMimeType(filePath: string): string {
284+ const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase()
285+ return mimeTypes[ext] || 'application/octet-stream'
286+ }
287+
288+ function isStaticAsset(pathname: string): boolean {
289+ if (pathname.startsWith('/assets/') || pathname.startsWith('/_assets/') || pathname.startsWith('/static/')) return true
290+ const ext = pathname.substring(pathname.lastIndexOf('.')).toLowerCase()
291+ return ext in mimeTypes && ext !== '.html' && ext !== '.htm'
292+ }
293+
175294const server = Bun.serve({
176295 port: process.env.PORT || 80,
177296 development: false,
178297 async fetch(request: Request): Promise<Response> {
179298 const url = new URL(request.url)
180- if (url.pathname === '/health') {
181- return new Response(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), version: '1.0.0' }), { headers: { 'Content-Type': 'application/json' } })
299+ const pathname = url.pathname
300+
301+ // Health check endpoint
302+ if (pathname === '/health') {
303+ return new Response(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), version: '1.0.0' }), {
304+ headers: { 'Content-Type': 'application/json' }
305+ })
182306 }
183- if (url.pathname === '/' || url.pathname === '/index.html') {
184- try {
185- const file = Bun.file('./resources/views/index.stx')
186- if (await file.exists()) {
187- return new Response(file, { headers: { 'Content-Type': 'text/html; charset=utf-8' } })
188- }
189- } catch (e) {}
307+
308+ // Handle static assets (CSS, JS, images, fonts, etc.)
309+ if (isStaticAsset(pathname)) {
310+ const assetPaths = [
311+ './public/dist' + pathname,
312+ './public' + pathname,
313+ './storage/public' + pathname,
314+ '.' + pathname,
315+ ]
316+ for (const assetPath of assetPaths) {
317+ try {
318+ const file = Bun.file(assetPath)
319+ if (await file.exists()) {
320+ return new Response(file, {
321+ headers: {
322+ 'Content-Type': getMimeType(pathname),
323+ 'Cache-Control': 'public, max-age=31536000, immutable',
324+ 'Access-Control-Allow-Origin': '*',
325+ }
326+ })
327+ }
328+ } catch {}
329+ }
330+ // Asset not found
331+ return new Response('Not Found', { status: 404, headers: { 'Content-Type': 'text/plain' } })
332+ }
333+
334+ // Serve index.html for root and HTML pages
335+ if (pathname === '/' || pathname === '/index.html' || pathname.endsWith('.html')) {
336+ const htmlPaths = [
337+ './public/dist/index.html',
338+ './public/index.html',
339+ './resources/views/index.stx',
340+ './resources/views/index.html',
341+ ]
342+ for (const htmlPath of htmlPaths) {
343+ try {
344+ const file = Bun.file(htmlPath)
345+ if (await file.exists()) {
346+ return new Response(file, { headers: { 'Content-Type': 'text/html; charset=utf-8' } })
347+ }
348+ } catch {}
349+ }
190350 }
191- return new Response(JSON.stringify({ message: 'Welcome to Stacks API!', path: url.pathname, method: request.method, timestamp: new Date().toISOString() }), { headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } })
351+
352+ // API fallback - return JSON for unmatched routes
353+ return new Response(JSON.stringify({ message: 'Welcome to Stacks API!', path: pathname, method: request.method, timestamp: new Date().toISOString() }), {
354+ headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
355+ })
192356 }
193357})
194358console.log('Stacks Server running at http://localhost:' + server.port)
0 commit comments