208 lines
7.0 KiB
Docker
208 lines
7.0 KiB
Docker
# Build stage
|
|
FROM node:18-alpine AS builder
|
|
|
|
# Install build dependencies for native modules (bcrypt, etc.)
|
|
RUN apk add --no-cache python3 make g++ libc6-compat
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy package files first for better Docker layer caching
|
|
COPY package*.json ./
|
|
|
|
# Install dependencies with proper npm cache handling
|
|
RUN npm ci --only=production=false --silent
|
|
|
|
# Copy source code
|
|
COPY . .
|
|
|
|
# Set build-time environment variables
|
|
ARG VITE_APP_NAME="ScriptShare"
|
|
ARG VITE_APP_URL="https://scriptshare.example.com"
|
|
ARG VITE_ANALYTICS_ENABLED="false"
|
|
|
|
# Export as environment variables for Vite build
|
|
ENV VITE_APP_NAME=$VITE_APP_NAME
|
|
ENV VITE_APP_URL=$VITE_APP_URL
|
|
ENV VITE_ANALYTICS_ENABLED=$VITE_ANALYTICS_ENABLED
|
|
|
|
# Remove problematic packages from package.json to prevent them from being bundled
|
|
RUN sed -i '/"mysql2"/d' package.json
|
|
RUN sed -i '/"drizzle-orm"/d' package.json
|
|
RUN sed -i '/"bcrypt"/d' package.json
|
|
RUN sed -i '/"jsonwebtoken"/d' package.json
|
|
RUN sed -i '/"@types\/bcrypt"/d' package.json
|
|
RUN sed -i '/"@types\/jsonwebtoken"/d' package.json
|
|
RUN sed -i '/"nanoid"/d' package.json
|
|
|
|
# Reinstall dependencies without server packages
|
|
RUN npm install
|
|
|
|
# Remove problematic server-side API files for frontend-only build
|
|
RUN rm -rf src/lib/api || true
|
|
RUN rm -rf src/lib/db || true
|
|
|
|
# Create mock API layer for frontend demo
|
|
RUN mkdir -p src/lib/api src/lib/db
|
|
|
|
# Create mock database files
|
|
RUN echo "export const db = {};" > src/lib/db/index.ts
|
|
RUN echo "export const users = {}; export const scripts = {}; export const ratings = {}; export const scriptVersions = {}; export const scriptAnalytics = {}; export const scriptCollections = {}; export const collectionScripts = {};" > src/lib/db/schema.ts
|
|
|
|
# Create comprehensive mock API files with proper TypeScript support
|
|
|
|
# Mock API index with proper types
|
|
RUN cat > src/lib/api/index.ts << 'EOFILE'
|
|
export const generateId = () => Math.random().toString(36).substr(2, 9);
|
|
export class ApiError extends Error {
|
|
constructor(message: string, public status: number) {
|
|
super(message);
|
|
this.status = status;
|
|
}
|
|
}
|
|
EOFILE
|
|
|
|
# Mock auth API with complete interface
|
|
RUN cat > src/lib/api/auth.ts << 'EOFILE'
|
|
export const authApi = {
|
|
login: async (data: any) => ({ token: 'demo', user: { id: '1', username: 'demo' } }),
|
|
register: async (data: any) => ({ token: 'demo', user: { id: '1', username: 'demo' } }),
|
|
changePassword: async (data: any) => ({}),
|
|
refreshToken: async () => ({ token: 'demo' })
|
|
};
|
|
EOFILE
|
|
|
|
# Mock scripts API with all required methods
|
|
RUN cat > src/lib/api/scripts.ts << 'EOFILE'
|
|
export interface ScriptFilters {
|
|
search?: string;
|
|
categories?: string[];
|
|
compatibleOs?: string[];
|
|
sortBy?: string;
|
|
limit?: number;
|
|
isApproved?: boolean;
|
|
}
|
|
export interface UpdateScriptData {
|
|
name?: string;
|
|
description?: string;
|
|
content?: string;
|
|
}
|
|
export const scriptsApi = {
|
|
getScripts: async (filters?: ScriptFilters) => ({ scripts: [], total: 0 }),
|
|
getScriptById: async (id: string) => null,
|
|
getPopularScripts: async () => [],
|
|
getRecentScripts: async () => [],
|
|
createScript: async (data: any) => ({ id: 'mock' }),
|
|
updateScript: async (id: string, data: UpdateScriptData, userId: string) => ({ id }),
|
|
deleteScript: async (id: string, userId: string) => ({}),
|
|
moderateScript: async (id: string, isApproved: boolean, moderatorId: string) => ({ id, isApproved }),
|
|
incrementViewCount: async (id: string) => ({}),
|
|
incrementDownloadCount: async (id: string) => ({})
|
|
};
|
|
EOFILE
|
|
|
|
# Mock ratings API with complete interface
|
|
RUN cat > src/lib/api/ratings.ts << 'EOFILE'
|
|
export const ratingsApi = {
|
|
submitRating: async (data: any) => ({ scriptId: data.scriptId }),
|
|
rateScript: async (data: any) => ({ scriptId: data.scriptId }),
|
|
getUserRating: async (scriptId: string, userId?: string) => null,
|
|
getScriptRatings: async (scriptId: string) => [],
|
|
getScriptRatingStats: async (scriptId: string) => ({ averageRating: 0, totalRatings: 0, distribution: {} }),
|
|
deleteRating: async (scriptId: string, userId: string) => ({})
|
|
};
|
|
EOFILE
|
|
|
|
# Mock analytics API with complete interface
|
|
RUN cat > src/lib/api/analytics.ts << 'EOFILE'
|
|
export interface AnalyticsFilters {
|
|
startDate?: Date;
|
|
endDate?: Date;
|
|
}
|
|
export const analyticsApi = {
|
|
trackEvent: async (data: any) => ({}),
|
|
getAnalytics: async () => ({ views: [], downloads: [], topScripts: [], userGrowth: [] }),
|
|
getAnalyticsEvents: async (filters?: AnalyticsFilters) => [],
|
|
getScriptAnalytics: async (scriptId: string) => ({ views: [], downloads: [] }),
|
|
getPlatformAnalytics: async () => ({ totalUsers: 0, totalScripts: 0 }),
|
|
getUserAnalytics: async (userId: string) => ({ views: [], downloads: [] })
|
|
};
|
|
EOFILE
|
|
|
|
# Mock collections API with complete interface
|
|
RUN cat > src/lib/api/collections.ts << 'EOFILE'
|
|
export interface UpdateCollectionData {
|
|
name?: string;
|
|
description?: string;
|
|
}
|
|
export const collectionsApi = {
|
|
getCollections: async () => [],
|
|
getCollectionById: async (id: string) => null,
|
|
getUserCollections: async (userId: string) => [],
|
|
getPublicCollections: async () => [],
|
|
createCollection: async (data: any) => ({ id: 'mock' }),
|
|
updateCollection: async (id: string, data: UpdateCollectionData) => ({ id }),
|
|
deleteCollection: async (id: string) => ({}),
|
|
addScriptToCollection: async (collectionId: string, scriptId: string) => ({}),
|
|
removeScriptFromCollection: async (collectionId: string, scriptId: string) => ({}),
|
|
isScriptInCollection: async (collectionId: string, scriptId: string) => false
|
|
};
|
|
EOFILE
|
|
|
|
# Mock users API with complete interface
|
|
RUN cat > src/lib/api/users.ts << 'EOFILE'
|
|
export interface UpdateUserData {
|
|
username?: string;
|
|
displayName?: string;
|
|
bio?: string;
|
|
}
|
|
export const usersApi = {
|
|
getUser: async (id: string) => null,
|
|
getUserById: async (id: string) => null,
|
|
getAllUsers: async () => [],
|
|
searchUsers: async (query: string) => [],
|
|
createUser: async (data: any) => ({ id: 'mock' }),
|
|
updateUser: async (id: string, data: UpdateUserData) => ({ id }),
|
|
updateUserPermissions: async (id: string, permissions: any) => ({ id })
|
|
};
|
|
EOFILE
|
|
|
|
# Build the application (frontend only with mocks)
|
|
RUN npm run build
|
|
|
|
# Verify build output exists
|
|
RUN ls -la /app/dist
|
|
|
|
# Production stage
|
|
FROM nginx:alpine
|
|
|
|
# Install curl for health checks
|
|
RUN apk add --no-cache curl
|
|
|
|
# Copy built files from builder stage
|
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
|
|
# Copy nginx configuration
|
|
COPY nginx.conf /etc/nginx/nginx.conf
|
|
|
|
# Create nginx pid directory
|
|
RUN mkdir -p /var/run/nginx
|
|
|
|
# Set proper permissions
|
|
RUN chown -R nginx:nginx /usr/share/nginx/html
|
|
RUN chown -R nginx:nginx /var/cache/nginx
|
|
RUN chown -R nginx:nginx /var/log/nginx
|
|
RUN chown -R nginx:nginx /var/run/nginx
|
|
|
|
# Switch to non-root user for security
|
|
USER nginx
|
|
|
|
# Expose port 80
|
|
EXPOSE 80
|
|
|
|
# Add healthcheck
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
|
CMD curl -f http://localhost/health || exit 1
|
|
|
|
# Start nginx
|
|
CMD ["nginx", "-g", "daemon off;"]
|