feat(csms): restructure deployment setup with Docker and improve build process

This commit is contained in:
2026-03-11 15:39:31 +08:00
parent 70ae7da0d9
commit ce53a4f218
12 changed files with 189 additions and 200 deletions

32
apps/csms/Dockerfile Normal file
View File

@@ -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"]

View File

@@ -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"]

View File

@@ -1,12 +0,0 @@
version: '3.8'
services:
csms:
build: .
ports:
- "3001:3001"
environment:
NODE_ENV: production
# env_file:
# - .env
restart: unless-stopped

View File

@@ -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);

View File

@@ -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"
}
}

View File

@@ -10,7 +10,9 @@ const allDeps = {
...packageJson.dependencies, ...packageJson.dependencies,
...packageJson.devDependencies, ...packageJson.devDependencies,
} }
const externalModules = Object.keys(allDeps) // 开发模式将依赖标记为 external 以加快构建速度;
// 生产模式全部打包进 bundlerunner 阶段无需 node_modules。
const externalModules = isProduction ? [] : Object.keys(allDeps)
const config = { const config = {
entryPoints: ['src/index.ts'], entryPoints: ['src/index.ts'],
@@ -22,6 +24,12 @@ const config = {
external: externalModules, external: externalModules,
sourcemap: !isProduction, sourcemap: !isProduction,
minify: isProduction, minify: isProduction,
// CJS 包(如 dotenv在 ESM bundle 中需要 require 支持
banner: isProduction
? {
js: `import{createRequire}from'module';const require=createRequire(import.meta.url);`,
}
: {},
define: { define: {
'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`, 'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`,
}, },

View File

@@ -6,8 +6,6 @@
"build": "node esbuild.config.js", "build": "node esbuild.config.js",
"build:prod": "NODE_ENV=production node esbuild.config.js", "build:prod": "NODE_ENV=production node esbuild.config.js",
"start": "node dist/index.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:auth": "npx @better-auth/cli generate --output src/db/auth-schema.ts",
"db:gen": "drizzle-kit generate", "db:gen": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate", "db:migrate": "drizzle-kit migrate",

View File

@@ -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)
}

134
apps/web/.dockerignore Normal file
View File

@@ -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*

View File

@@ -1,7 +1,7 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ output: "standalone",
}; };
export default nextConfig; export default nextConfig;

12
docker-compose.yml Normal file
View File

@@ -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

View File

@@ -5,7 +5,7 @@
"description": "Helios EV Charging Station Management System", "description": "Helios EV Charging Station Management System",
"scripts": { "scripts": {
"dev:csms": "pnpm --filter csms dev", "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", "start:csms": "pnpm --filter csms start",
"dev:web": "pnpm --filter web dev", "dev:web": "pnpm --filter web dev",
"build:web": "pnpm --filter web build", "build:web": "pnpm --filter web build",