From ce53a4f218242af7ce4e62773a2b02ebd474665c Mon Sep 17 00:00:00 2001 From: Timothy Yin Date: Wed, 11 Mar 2026 15:39:31 +0800 Subject: [PATCH] feat(csms): restructure deployment setup with Docker and improve build process --- apps/csms/Dockerfile | 32 ++++++ apps/csms/deploy/Dockerfile | 16 --- apps/csms/deploy/docker-compose.yml | 12 --- apps/csms/deploy/index.js | 1 - apps/csms/deploy/package.json | 13 --- apps/csms/esbuild.config.js | 10 +- apps/csms/package.json | 2 - apps/csms/scripts/deploy.js | 153 ---------------------------- apps/web/.dockerignore | 134 ++++++++++++++++++++++++ apps/web/next.config.ts | 2 +- docker-compose.yml | 12 +++ package.json | 2 +- 12 files changed, 189 insertions(+), 200 deletions(-) create mode 100644 apps/csms/Dockerfile delete mode 100644 apps/csms/deploy/Dockerfile delete mode 100644 apps/csms/deploy/docker-compose.yml delete mode 100644 apps/csms/deploy/index.js delete mode 100644 apps/csms/deploy/package.json delete mode 100755 apps/csms/scripts/deploy.js create mode 100644 apps/web/.dockerignore create mode 100644 docker-compose.yml diff --git a/apps/csms/Dockerfile b/apps/csms/Dockerfile new file mode 100644 index 0000000..ccc4d7c --- /dev/null +++ b/apps/csms/Dockerfile @@ -0,0 +1,32 @@ +FROM node:22-alpine AS base + +FROM base AS builder + +RUN apk add --no-cache gcompat +RUN corepack enable && corepack prepare pnpm@10.18.2 --activate +WORKDIR /app + +# 复制 monorepo 根配置(catalog、lockfile 等) +COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./ +# 复制 csms 应用源码 +COPY apps/csms/package.json ./apps/csms/package.json +COPY apps/csms/esbuild.config.js ./apps/csms/esbuild.config.js +COPY apps/csms/tsconfig.json ./apps/csms/tsconfig.json +COPY apps/csms/src ./apps/csms/src + +RUN pnpm install --filter csms && \ + pnpm --filter csms run build:prod + +FROM base AS runner +WORKDIR /app + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 hono + +COPY --from=builder --chown=hono:nodejs /app/apps/csms/dist /app/dist +COPY --from=builder --chown=hono:nodejs /app/apps/csms/package.json /app/package.json + +USER hono +EXPOSE 3001 + +CMD ["node", "/app/dist/index.js"] diff --git a/apps/csms/deploy/Dockerfile b/apps/csms/deploy/Dockerfile deleted file mode 100644 index d880a00..0000000 --- a/apps/csms/deploy/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:22-alpine - -WORKDIR /app - -# 复制应用文件 -COPY package.json . -COPY index.js . - -# 安装依赖 -RUN npm ci --omit=dev - -# 暴露端口 -EXPOSE 3001 - -# 启动应用 -CMD ["node", "index.js"] diff --git a/apps/csms/deploy/docker-compose.yml b/apps/csms/deploy/docker-compose.yml deleted file mode 100644 index 922abde..0000000 --- a/apps/csms/deploy/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: '3.8' - -services: - csms: - build: . - ports: - - "3001:3001" - environment: - NODE_ENV: production - # env_file: - # - .env - restart: unless-stopped diff --git a/apps/csms/deploy/index.js b/apps/csms/deploy/index.js deleted file mode 100644 index 7cc9bfe..0000000 --- a/apps/csms/deploy/index.js +++ /dev/null @@ -1 +0,0 @@ -import{serve as c}from"@hono/node-server";import{Hono as a}from"hono";import{createNodeWebSocket as p}from"@hono/node-ws";import{getConnInfo as i}from"@hono/node-server/conninfo";import{cors as m}from"hono/cors";import{logger as l}from"hono/logger";import{showRoutes as g}from"hono/dev";import{readFileSync as d}from"fs";import{dirname as f,join as u}from"path";import{fileURLToPath as h}from"url";var S=f(h(import.meta.url)),s="1.0.0";try{let o=u(S,"../../package.json");s=JSON.parse(d(o,"utf-8")).version||"1.0.0"}catch{}var r=new a,{injectWebSocket:k,upgradeWebSocket:v}=p({app:r});r.use(l());r.use("/ocpp",m({origin:"*",allowMethods:["GET","POST","OPTIONS"],allowHeaders:["Content-Type","Authorization"],exposeHeaders:["Content-Length"],credentials:!0}));r.get("/",o=>o.json({platform:"Helios CSMS",version:s,message:"ok"}));r.get("/ocpp",v(o=>({onOpen(e,t){let n=i(o);console.log(`New connection from ${n.remote.address}:${n.remote.port}`)},onMessage(e,t){console.log(`Received message: ${e.data}`),t.send(`Echo: ${e.data}`)},onClose(e,t){console.log("Connection closed: ",e.code,e.reason)}})));g(r,{verbose:!0});var w=c({fetch:r.fetch,port:3001},o=>{console.log(`Server is running on http://localhost:${o.port}`)});k(w); diff --git a/apps/csms/deploy/package.json b/apps/csms/deploy/package.json deleted file mode 100644 index 8bb3195..0000000 --- a/apps/csms/deploy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "csms", - "type": "module", - "dependencies": { - "@hono/node-server": "^1.19.6", - "@hono/node-ws": "^1.2.0", - "better-auth": "^1.3.34", - "dotenv": "^17.2.3", - "drizzle-orm": "^0.44.7", - "hono": "^4.10.6", - "pg": "^8.16.3" - } -} \ No newline at end of file diff --git a/apps/csms/esbuild.config.js b/apps/csms/esbuild.config.js index 72c0379..022a485 100644 --- a/apps/csms/esbuild.config.js +++ b/apps/csms/esbuild.config.js @@ -10,7 +10,9 @@ const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, } -const externalModules = Object.keys(allDeps) +// 开发模式将依赖标记为 external 以加快构建速度; +// 生产模式全部打包进 bundle,runner 阶段无需 node_modules。 +const externalModules = isProduction ? [] : Object.keys(allDeps) const config = { entryPoints: ['src/index.ts'], @@ -22,6 +24,12 @@ const config = { external: externalModules, sourcemap: !isProduction, minify: isProduction, + // CJS 包(如 dotenv)在 ESM bundle 中需要 require 支持 + banner: isProduction + ? { + js: `import{createRequire}from'module';const require=createRequire(import.meta.url);`, + } + : {}, define: { 'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`, }, diff --git a/apps/csms/package.json b/apps/csms/package.json index 8887970..557c4ce 100644 --- a/apps/csms/package.json +++ b/apps/csms/package.json @@ -6,8 +6,6 @@ "build": "node esbuild.config.js", "build:prod": "NODE_ENV=production node esbuild.config.js", "start": "node dist/index.js", - "deploy": "node scripts/deploy.js", - "deploy:docker": "node scripts/deploy.js --docker", "db:gen:auth": "npx @better-auth/cli generate --output src/db/auth-schema.ts", "db:gen": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", diff --git a/apps/csms/scripts/deploy.js b/apps/csms/scripts/deploy.js deleted file mode 100755 index cb7d5b5..0000000 --- a/apps/csms/scripts/deploy.js +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env node - -/** - * 部署助手脚本 - * - * 使用: - * node scripts/deploy.js # 标准部署(生产构建 + 依赖) - * node scripts/deploy.js --docker # Docker 镜像 - * node scripts/deploy.js --help # 显示帮助 - */ - -import fs from 'fs' -import path from 'path' -import { execSync } from 'child_process' - -const args = process.argv.slice(2) -const help = args.includes('--help') || args.includes('-h') -const isDocker = args.includes('--docker') -const outDir = args.includes('--out') ? args[args.indexOf('--out') + 1] : 'dist' - -if (help) { - console.log(` -Usage: node scripts/deploy.js [options] - -Options: - --docker 生成 Dockerfile - --out PATH 输出目录(默认: dist) - --help 显示此帮助信息 - -Examples: - node scripts/deploy.js 生产构建 - node scripts/deploy.js --out build 输出到 build 目录 - node scripts/deploy.js --docker 生成 Docker 配置 -`) - process.exit(0) -} - -console.log('🚀 开始部署流程...\n') - -try { - // 1. 生产构建 - console.log('📦 构建应用...') - execSync('npm run build:prod', { stdio: 'inherit', cwd: process.cwd() }) - - // 2. 创建部署目录 - console.log('\n📁 准备部署目录...') - const deployDir = path.join(process.cwd(), 'deploy') - - if (fs.existsSync(deployDir)) { - fs.rmSync(deployDir, { recursive: true }) - } - fs.mkdirSync(deployDir, { recursive: true }) - - // 3. 复制必要文件 - console.log('📋 复制文件...') - - // 复制构建输出 - fs.copyFileSync( - path.join(process.cwd(), 'dist/index.js'), - path.join(deployDir, 'index.js'), - ) - fs.copyFileSync( - path.join(process.cwd(), 'dist/package.json'), - path.join(deployDir, 'package.json'), - ) - - // 复制 package.json(用于 npm install) - const srcPkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')) - const deployPkg = { - name: srcPkg.name, - version: srcPkg.version, - type: 'module', - dependencies: srcPkg.dependencies, - } - fs.writeFileSync( - path.join(deployDir, 'package.json'), - JSON.stringify(deployPkg, null, 2), - ) - - // 复制 .env 模板 - if (fs.existsSync('.env.example')) { - fs.copyFileSync('.env.example', path.join(deployDir, '.env.example')) - } - - console.log(`✅ 部署文件已生成到 ${path.relative(process.cwd(), deployDir)}/`) - - // 4. 可选:生成 Docker 配置 - if (isDocker) { - console.log('\n🐳 生成 Docker 配置...') - - const dockerfile = `FROM node:22-alpine - -WORKDIR /app - -# 复制应用文件 -COPY package.json . -COPY index.js . - -# 安装依赖 -RUN npm ci --omit=dev - -# 暴露端口 -EXPOSE 3001 - -# 启动应用 -CMD ["node", "index.js"] -` - - const dockerCompose = `version: '3.8' - -services: - csms: - build: . - ports: - - "3001:3001" - environment: - NODE_ENV: production - # env_file: - # - .env - restart: unless-stopped -` - - fs.writeFileSync(path.join(deployDir, 'Dockerfile'), dockerfile) - fs.writeFileSync(path.join(deployDir, 'docker-compose.yml'), dockerCompose) - - console.log('✅ Docker 文件已生成') - } - - // 5. 显示部署信息 - console.log('\n📊 部署信息:') - console.log(` 名称: ${deployPkg.name}`) - console.log(` 版本: ${deployPkg.version}`) - console.log(` 主文件: index.js`) - console.log(` 依赖数: ${Object.keys(deployPkg.dependencies).length}`) - - const indexSize = fs.statSync(path.join(deployDir, 'index.js')).size - console.log(` 代码大小: ${(indexSize / 1024).toFixed(1)}KB`) - - console.log('\n✨ 部署准备完成!\n') - console.log('下一步:') - console.log(` 1. cd ${path.relative(process.cwd(), deployDir)}`) - console.log(` 2. npm install --omit=dev`) - console.log(` 3. node index.js`) - - if (isDocker) { - console.log('\n或使用 Docker:') - console.log(` 1. cd ${path.relative(process.cwd(), deployDir)}`) - console.log(` 2. docker compose up`) - } -} catch (error) { - console.error('\n❌ 部署失败:', error.message) - process.exit(1) -} diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore new file mode 100644 index 0000000..a8f13a8 --- /dev/null +++ b/apps/web/.dockerignore @@ -0,0 +1,134 @@ +############################################################ +# Production-ready .dockerignore for a Next.js (Vercel-style) app +# Keeps Docker builds fast, lean, and free of development files. +############################################################ + +# Dependencies (installed inside Docker, never copied) +node_modules/ +.pnpm-store/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Next.js build outputs (always generated during `next build`) +.next/ +out/ +dist/ +build/ +.vercel/ + +# Tests and testing output (not needed in production images) +coverage/ +.nyc_output/ +__tests__/ +__mocks__/ +jest/ +cypress/ +cypress/screenshots/ +cypress/videos/ +playwright-report/ +test-results/ +.vitest/ +vitest.config.* +jest.config.* +cypress.config.* +playwright.config.* +*.test.* +*.spec.* + +# Local development and editor files +.git/ +.gitignore +.gitattributes +.vscode/ +.idea/ +*.swp +*.swo +*~ +*.log + +# Environment variables (only commit template files) +.env +.env*.local +.env.development +.env.test +.env.production.local + +# Docker configuration files (not needed inside build context) +Dockerfile* +.dockerignore +compose.yaml +compose.yml +docker-compose*.yaml +docker-compose*.yml + +# Documentation +*.md +docs/ + +# CI/CD configuration files +.github/ +.gitlab-ci.yml +.travis.yml +.circleci/ +Jenkinsfile + +# Cache directories and temporary data +.cache/ +.parcel-cache/ +.eslintcache +.stylelintcache +.swc/ +.turbo/ +.tmp/ +.temp/ + +# TypeScript build metadata +*.tsbuildinfo + +# Sensitive or unnecessary configuration files +*.pem +.editorconfig +.prettierrc* +prettier.config.* +.eslintrc* +eslint.config.* +.stylelintrc* +stylelint.config.* +.babelrc* +*.iml +*.ipr +*.iws + +# OS-specific junk +.DS_Store +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +Desktop.ini + +# AI/ML tool metadata and configs +.cursor/ +.cursorrules +.copilot/ +.copilotignore +.github/copilot/ +.gemini/ +.anthropic/ +.kiro +.claude +AGENTS.md +.agents/ + +# AI-generated temp files +*.aider* +*.copilot* +*.chatgpt* +*.claude* +*.gemini* +*.openai* +*.anthropic* diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index e9ffa30..68a6c64 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "standalone", }; export default nextConfig; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..662d2c5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +name: helios-evcs + +services: + csms: + container_name: helios-csms + build: + context: . + dockerfile: apps/csms/Dockerfile + ports: + - "3001:3001" + env_file: + - apps/csms/.env diff --git a/package.json b/package.json index 5c35829..df3f7c4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Helios EV Charging Station Management System", "scripts": { "dev:csms": "pnpm --filter csms dev", - "build:csms": "pnpm --filter csms build", + "build:csms": "pnpm --filter csms build:prod", "start:csms": "pnpm --filter csms start", "dev:web": "pnpm --filter web dev", "build:web": "pnpm --filter web build",