diff --git a/website/client/package-lock.json b/website/client/package-lock.json index 6aca096..acbf2e7 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -1,9 +1,10 @@ { - "name": "app", + "name": "repomix-website-client", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "repomix-website-client", "dependencies": { "jszip": "^3.10.1", "lucide-vue-next": "^0.474.0", diff --git a/website/client/package.json b/website/client/package.json index f08807c..fc55127 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -1,4 +1,5 @@ { + "name": "repomix-website-client", "private": true, "type": "module", "scripts": { diff --git a/website/server/Dockerfile b/website/server/Dockerfile index c00457d..80072b0 100644 --- a/website/server/Dockerfile +++ b/website/server/Dockerfile @@ -1,18 +1,13 @@ # ============================================================================== # Base image # ============================================================================== -FROM node:23-alpine AS builder - -# Install git and other dependencies -RUN apk add --no-cache \ - git \ - ca-certificates +FROM node:24-alpine AS builder WORKDIR /app COPY package*.json ./ -# Install dependencies -RUN npm i +# Install all dependencies (including dev dependencies for build) +RUN npm ci # Copy source code COPY . . @@ -21,33 +16,46 @@ COPY . . RUN npm run build # ============================================================================== -# Production image +# Production dependencies # ============================================================================== -FROM node:23-alpine +FROM node:24-alpine AS deps -# Install git and other dependencies -RUN apk add --no-cache \ - git \ - ca-certificates \ - curl +WORKDIR /app +COPY package*.json ./ + +# Install only production dependencies +RUN npm ci --only=production --ignore-scripts && \ + npm cache clean --force + +# ============================================================================== +# Runtime image +# ============================================================================== +FROM node:24-alpine + +# Install git and ca-certificates (required by repomix for remote repository processing) +RUN apk add --no-cache git ca-certificates + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 WORKDIR /app -# Copy only necessary files -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/package*.json ./ -COPY --from=builder /app/node_modules ./node_modules +# Copy built application +COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist + +# Copy production dependencies +COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules # Set environment variables ENV NODE_ENV=production \ PORT=8080 +# Switch to non-root user +USER nodejs + # Expose port EXPOSE 8080 -# Health check -HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ - CMD curl -f http://localhost:8080/health || exit 1 - -# Start the server -CMD ["npm", "start"] +# Start the server directly +CMD ["node", "dist/index.js"] diff --git a/website/server/cloudbuild.yaml b/website/server/cloudbuild.yaml index 05bfe96..56a0258 100644 --- a/website/server/cloudbuild.yaml +++ b/website/server/cloudbuild.yaml @@ -38,13 +38,13 @@ steps: - '--memory' - '2048Mi' - '--cpu' - - '4' + - '2' - '--min-instances' - '0' - '--max-instances' - '10' - '--timeout' - - '35s' + - '31s' - '--ingress' - 'all' - '--allow-unauthenticated' diff --git a/website/server/package-lock.json b/website/server/package-lock.json index 49f6bb5..54298b0 100644 --- a/website/server/package-lock.json +++ b/website/server/package-lock.json @@ -1,15 +1,15 @@ { - "name": "app", + "name": "repomix-website-server", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "repomix-website-server", "dependencies": { "@google-cloud/logging-winston": "^6.0.0", "@hono/node-server": "^1.13.8", "adm-zip": "^0.5.16", "hono": "^4.6.20", - "pako": "^2.1.0", "repomix": "^0.3.7", "winston": "^3.17.0", "zod": "^3.24.1" @@ -17,7 +17,6 @@ "devDependencies": { "@types/adm-zip": "^0.5.7", "@types/node": "^22.13.0", - "@types/pako": "^2.0.3", "rimraf": "^6.0.1", "tsx": "^4.19.2", "typescript": "^5.7.3" @@ -1183,13 +1182,6 @@ "undici-types": "~6.20.0" } }, - "node_modules/@types/pako": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", - "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/parse-path": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", @@ -3427,12 +3419,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, "node_modules/parse-path": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", @@ -3706,9 +3692,9 @@ } }, "node_modules/repomix": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/repomix/-/repomix-0.3.7.tgz", - "integrity": "sha512-/b0DKC6MIVVsjcJTkTiWRwNwJwx0u+1Ph1LMEKkID5txN6mhHUFXuHdyF1ZDklyjlWzEEt6gOBWMzc3sJB+s/Q==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/repomix/-/repomix-0.3.9.tgz", + "integrity": "sha512-Olo/vORZChL98HOC3tZaE5+kwSaoox8KoF9N+lfQRb7dWT4qNa/SLFHT40Xq54cbZ7l9qTIZhXWmU1d/AtwqGQ==", "license": "MIT", "dependencies": { "@clack/prompts": "^0.10.1", diff --git a/website/server/package.json b/website/server/package.json index f542a67..2a84011 100644 --- a/website/server/package.json +++ b/website/server/package.json @@ -1,4 +1,5 @@ { + "name": "repomix-website-server", "private": true, "type": "module", "scripts": { @@ -15,7 +16,6 @@ "@hono/node-server": "^1.13.8", "adm-zip": "^0.5.16", "hono": "^4.6.20", - "pako": "^2.1.0", "repomix": "^0.3.7", "winston": "^3.17.0", "zod": "^3.24.1" @@ -23,7 +23,6 @@ "devDependencies": { "@types/adm-zip": "^0.5.7", "@types/node": "^22.13.0", - "@types/pako": "^2.0.3", "rimraf": "^6.0.1", "tsx": "^4.19.2", "typescript": "^5.7.3" diff --git a/website/server/src/processZipFile.ts b/website/server/src/processZipFile.ts index 5535501..3f736bd 100644 --- a/website/server/src/processZipFile.ts +++ b/website/server/src/processZipFile.ts @@ -54,7 +54,7 @@ export async function processZipFile( ); // Check if the result is already cached - const cachedResult = cache.get(cacheKey); + const cachedResult = await cache.get(cacheKey); if (cachedResult) { return cachedResult; } @@ -123,7 +123,7 @@ export async function processZipFile( }; // Save the result to cache - cache.set(cacheKey, packResultData); + await cache.set(cacheKey, packResultData); return packResultData; } catch (error) { diff --git a/website/server/src/remoteRepo.ts b/website/server/src/remoteRepo.ts index 741924b..1e1f9c6 100644 --- a/website/server/src/remoteRepo.ts +++ b/website/server/src/remoteRepo.ts @@ -35,7 +35,7 @@ export async function processRemoteRepo( const cacheKey = generateCacheKey(validatedData.url, validatedData.format, validatedData.options, 'url'); // Check if the result is already cached - const cachedResult = cache.get(cacheKey); + const cachedResult = await cache.get(cacheKey); if (cachedResult) { return cachedResult; } @@ -100,7 +100,7 @@ export async function processRemoteRepo( }; // Save the result to cache - cache.set(cacheKey, packResultData); + await cache.set(cacheKey, packResultData); return packResultData; } catch (error) { diff --git a/website/server/src/utils/cache.ts b/website/server/src/utils/cache.ts index 705b963..ef34ec1 100644 --- a/website/server/src/utils/cache.ts +++ b/website/server/src/utils/cache.ts @@ -1,13 +1,17 @@ -import pako from 'pako'; +import { promisify } from 'node:util'; +import * as zlib from 'node:zlib'; import type { PackOptions } from '../types.js'; -interface CacheEntry { +const inflateAsync = promisify(zlib.inflate); +const deflateAsync = promisify(zlib.deflate); + +interface CacheEntry { value: Uint8Array; // Compressed data timestamp: number; } export class RequestCache { - private cache: Map> = new Map(); + private cache: Map = new Map(); private readonly ttl: number; constructor(ttlInSeconds = 60) { @@ -17,7 +21,7 @@ export class RequestCache { setInterval(() => this.cleanup(), ttlInSeconds * 1000); } - get(key: string): T | undefined { + async get(key: string): Promise { const entry = this.cache.get(key); if (!entry) { return undefined; @@ -31,8 +35,8 @@ export class RequestCache { try { // Decompress and return the data - const decompressedData = pako.inflate(entry.value, { to: 'string' }); - return JSON.parse(decompressedData); + const decompressedData = await inflateAsync(entry.value); + return JSON.parse(decompressedData.toString('utf8')); } catch (error) { console.error('Error decompressing cache entry:', error); this.cache.delete(key); @@ -40,11 +44,11 @@ export class RequestCache { } } - set(key: string, value: T): void { + async set(key: string, value: T): Promise { try { // Convert data to JSON string and compress const jsonString = JSON.stringify(value); - const compressedData = pako.deflate(jsonString); + const compressedData = await deflateAsync(Buffer.from(jsonString, 'utf8')); this.cache.set(key, { value: compressedData,