From 6e9d41bcf52fe24129b445fa16feb2581cd8ebd5 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 15:40:15 +0900 Subject: [PATCH 01/10] feat(website): Optimize Docker image size from 342MB to 304MB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement multi-stage build with production-only dependencies to improve Cloud Run startup performance. Reduce image size by 11% while maintaining all functionality including git support for remote repository processing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/Dockerfile | 54 ++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/website/server/Dockerfile b/website/server/Dockerfile index c00457d..eeb1f3b 100644 --- a/website/server/Dockerfile +++ b/website/server/Dockerfile @@ -3,16 +3,11 @@ # ============================================================================== FROM node:23-alpine AS builder -# Install git and other dependencies -RUN apk add --no-cache \ - git \ - ca-certificates - 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 AS deps + +WORKDIR /app +COPY package*.json ./ + +# Install only production dependencies +RUN npm ci --only=production --ignore-scripts && \ + npm cache clean --force + +# ============================================================================== +# Runtime image # ============================================================================== FROM node:23-alpine -# Install git and other dependencies -RUN apk add --no-cache \ - git \ - ca-certificates \ - curl +# Install git (required by repomix for remote repository processing) +RUN apk add --no-cache git + +# 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"] From cae53523a7c4f6cbce08aeb517a06f8471d0b564 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 16:26:59 +0900 Subject: [PATCH 02/10] refactor(website/server): Replace pako with Node.js zlib for compression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace pako.deflate/inflate with zlib.deflateSync/inflateSync in cache.ts - Remove pako and @types/pako dependencies from package.json - Utilize Node.js built-in zlib module for better performance and reduced dependencies 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/package-lock.json | 15 --------------- website/server/package.json | 2 -- website/server/src/utils/cache.ts | 6 +++--- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/website/server/package-lock.json b/website/server/package-lock.json index 49f6bb5..c8c7647 100644 --- a/website/server/package-lock.json +++ b/website/server/package-lock.json @@ -9,7 +9,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" @@ -17,7 +16,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 +1181,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 +3418,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", diff --git a/website/server/package.json b/website/server/package.json index f542a67..74cada0 100644 --- a/website/server/package.json +++ b/website/server/package.json @@ -15,7 +15,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 +22,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/utils/cache.ts b/website/server/src/utils/cache.ts index 705b963..2725ae9 100644 --- a/website/server/src/utils/cache.ts +++ b/website/server/src/utils/cache.ts @@ -1,4 +1,4 @@ -import pako from 'pako'; +import * as zlib from 'node:zlib'; import type { PackOptions } from '../types.js'; interface CacheEntry { @@ -31,7 +31,7 @@ export class RequestCache { try { // Decompress and return the data - const decompressedData = pako.inflate(entry.value, { to: 'string' }); + const decompressedData = zlib.inflateSync(entry.value).toString('utf8'); return JSON.parse(decompressedData); } catch (error) { console.error('Error decompressing cache entry:', error); @@ -44,7 +44,7 @@ export class RequestCache { try { // Convert data to JSON string and compress const jsonString = JSON.stringify(value); - const compressedData = pako.deflate(jsonString); + const compressedData = zlib.deflateSync(Buffer.from(jsonString, 'utf8')); this.cache.set(key, { value: compressedData, From c394451094faae2059552613f714748ac890bfad Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 16:27:12 +0900 Subject: [PATCH 03/10] config(website/server): Reduce Cloud Run CPU allocation from 4 to 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Optimize resource allocation in cloudbuild.yaml for cost efficiency - Maintain adequate performance with reduced CPU allocation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/cloudbuild.yaml b/website/server/cloudbuild.yaml index 05bfe96..a0b2c65 100644 --- a/website/server/cloudbuild.yaml +++ b/website/server/cloudbuild.yaml @@ -38,7 +38,7 @@ steps: - '--memory' - '2048Mi' - '--cpu' - - '4' + - '2' - '--min-instances' - '0' - '--max-instances' From 075880798f91a8c3b26662963ad68700f356f3eb Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 16:28:31 +0900 Subject: [PATCH 04/10] chore(website/server): Update repomix to v0.3.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update repomix dependency from 0.3.7 to 0.3.9 - Includes latest features and bug fixes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/server/package-lock.json b/website/server/package-lock.json index c8c7647..4ffbdcb 100644 --- a/website/server/package-lock.json +++ b/website/server/package-lock.json @@ -1,5 +1,5 @@ { - "name": "app", + "name": "server", "lockfileVersion": 3, "requires": true, "packages": { @@ -3691,9 +3691,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", From 68448d1d8aeb4fd527272fef7925eaa76d65e812 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 16:35:03 +0900 Subject: [PATCH 05/10] fix(website): Add explicit package names to prevent Docker container conflicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add "name": "repomix-website-server" to server/package.json - Add "name": "repomix-website-client" to client/package.json - Prevents npm from using /app directory name in package-lock.json during Docker builds - Ensures consistent package names across development environments 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/client/package-lock.json | 3 ++- website/client/package.json | 1 + website/server/package-lock.json | 3 ++- website/server/package.json | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) 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/package-lock.json b/website/server/package-lock.json index 4ffbdcb..54298b0 100644 --- a/website/server/package-lock.json +++ b/website/server/package-lock.json @@ -1,9 +1,10 @@ { - "name": "server", + "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", diff --git a/website/server/package.json b/website/server/package.json index 74cada0..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": { From 77d830586e9d249d9a3e89c0f7bd67fda187aab5 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 17:09:11 +0900 Subject: [PATCH 06/10] config(website/server): Reduce Cloud Run timeout from 35s to 31s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/cloudbuild.yaml b/website/server/cloudbuild.yaml index a0b2c65..56a0258 100644 --- a/website/server/cloudbuild.yaml +++ b/website/server/cloudbuild.yaml @@ -44,7 +44,7 @@ steps: - '--max-instances' - '10' - '--timeout' - - '35s' + - '31s' - '--ingress' - 'all' - '--allow-unauthenticated' From 1affddbe8890871fd8f59ee979ccdc8ccca31e57 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 17:18:40 +0900 Subject: [PATCH 07/10] chore(website/server): Update Node.js from 23 to 24 in Dockerfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/server/Dockerfile b/website/server/Dockerfile index eeb1f3b..5e0b6a3 100644 --- a/website/server/Dockerfile +++ b/website/server/Dockerfile @@ -1,7 +1,7 @@ # ============================================================================== # Base image # ============================================================================== -FROM node:23-alpine AS builder +FROM node:24-alpine AS builder WORKDIR /app COPY package*.json ./ @@ -18,7 +18,7 @@ RUN npm run build # ============================================================================== # Production dependencies # ============================================================================== -FROM node:23-alpine AS deps +FROM node:24-alpine AS deps WORKDIR /app COPY package*.json ./ @@ -30,7 +30,7 @@ RUN npm ci --only=production --ignore-scripts && \ # ============================================================================== # Runtime image # ============================================================================== -FROM node:23-alpine +FROM node:24-alpine # Install git (required by repomix for remote repository processing) RUN apk add --no-cache git From 90989c641315ee00b897baec5746261a75d2347d Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 17:22:30 +0900 Subject: [PATCH 08/10] perf(website/server): Make zlib operations asynchronous to prevent event loop blocking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace synchronous zlib.inflateSync/deflateSync with async alternatives to prevent blocking the event loop under high load. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/src/processZipFile.ts | 4 ++-- website/server/src/remoteRepo.ts | 4 ++-- website/server/src/utils/cache.ts | 14 +++++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) 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 2725ae9..a72fa0a 100644 --- a/website/server/src/utils/cache.ts +++ b/website/server/src/utils/cache.ts @@ -1,6 +1,10 @@ import * as zlib from 'node:zlib'; +import { promisify } from 'node:util'; import type { PackOptions } from '../types.js'; +const inflateAsync = promisify(zlib.inflate); +const deflateAsync = promisify(zlib.deflate); + interface CacheEntry { value: Uint8Array; // Compressed data timestamp: number; @@ -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 = zlib.inflateSync(entry.value).toString('utf8'); - 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 = zlib.deflateSync(Buffer.from(jsonString, 'utf8')); + const compressedData = await deflateAsync(Buffer.from(jsonString, 'utf8')); this.cache.set(key, { value: compressedData, From 900bd4936deace2394faf20bcd7642765a3f6d6a Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 17:23:27 +0900 Subject: [PATCH 09/10] fix(website/server): Remove unused type parameter from CacheEntry interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/src/utils/cache.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/server/src/utils/cache.ts b/website/server/src/utils/cache.ts index a72fa0a..ef34ec1 100644 --- a/website/server/src/utils/cache.ts +++ b/website/server/src/utils/cache.ts @@ -1,17 +1,17 @@ -import * as zlib from 'node:zlib'; import { promisify } from 'node:util'; +import * as zlib from 'node:zlib'; import type { PackOptions } from '../types.js'; const inflateAsync = promisify(zlib.inflate); const deflateAsync = promisify(zlib.deflate); -interface CacheEntry { +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) { From d58702ed1e57bc4d81bbae6ee88b6157c9504cbb Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sat, 7 Jun 2025 17:32:32 +0900 Subject: [PATCH 10/10] fix(website/server): Add ca-certificates to Alpine runtime image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alpine images often omit CA certificates needed for HTTPS git operations. Add ca-certificates to ensure remote cloning/pulls succeed at runtime. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- website/server/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/server/Dockerfile b/website/server/Dockerfile index 5e0b6a3..80072b0 100644 --- a/website/server/Dockerfile +++ b/website/server/Dockerfile @@ -32,8 +32,8 @@ RUN npm ci --only=production --ignore-scripts && \ # ============================================================================== FROM node:24-alpine -# Install git (required by repomix for remote repository processing) -RUN apk add --no-cache git +# 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 && \