本文目录导读:

我来帮你编写一个自动打包并上传的脚本,以下是几种常见场景的示例:
Python项目打包上传到PyPI
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
自动打包并上传到PyPI的脚本
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path
def clean_build():
"""清理之前的构建文件"""
dirs_to_clean = ['build', 'dist', '*.egg-info']
for dir_pattern in dirs_to_clean:
for path in Path('.').glob(dir_pattern):
if path.is_dir():
shutil.rmtree(path)
print(f"清理目录: {path}")
def bump_version(version_type='patch'):
"""自动更新版本号"""
# 使用bump2version或手动更新
try:
subprocess.run(['bump2version', version_type], check=True)
print(f"版本已更新 ({version_type})")
except subprocess.CalledProcessError:
print("请安装bump2version: pip install bump2version")
sys.exit(1)
def build_package():
"""构建包"""
print("开始构建包...")
try:
subprocess.run([sys.executable, '-m', 'build'], check=True)
print("包构建成功!")
except subprocess.CalledProcessError:
print("构建失败")
sys.exit(1)
def upload_to_pypi(repository='pypi'):
"""上传到PyPI"""
print(f"上传到 {repository}...")
# 检查是否有twine
try:
subprocess.run(['twine', '--version'], check=True, capture_output=True)
except subprocess.CalledProcessError:
print("请安装twine: pip install twine")
sys.exit(1)
try:
if repository == 'testpypi':
cmd = ['twine', 'upload', '--repository', 'testpypi', 'dist/*']
else:
cmd = ['twine', 'upload', 'dist/*']
subprocess.run(cmd, check=True)
print(f"上传到 {repository} 成功!")
except subprocess.CalledProcessError:
print(f"上传到 {repository} 失败")
sys.exit(1)
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='自动打包并上传Python包')
parser.add_argument('--bump', choices=['major', 'minor', 'patch'],
help='更新版本号类型')
parser.add_argument('--test', action='store_true',
help='上传到TestPyPI')
parser.add_argument('--skip-clean', action='store_true',
help='跳过清理步骤')
args = parser.parse_args()
# 1. 更新版本(可选)
if args.bump:
bump_version(args.bump)
# 2. 清理
if not args.skip_clean:
clean_build()
# 3. 构建
build_package()
# 4. 上传
if args.test:
upload_to_pypi('testpypi')
else:
upload_to_pypi()
print("打包上传完成!")
if __name__ == '__main__':
main()
Docker镜像打包上传
#!/bin/bash
# docker-build-and-push.sh
# Docker镜像自动打包上传脚本
set -e # 遇到错误立即退出
# 配置
DOCKER_REGISTRY="registry.example.com"
IMAGE_NAME="myapp"
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "latest")
DOCKERFILE="Dockerfile"
CONTEXT="."
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查依赖
check_dependencies() {
if ! command -v docker &> /dev/null; then
print_error "Docker未安装"
exit 1
fi
}
# 登录到仓库
login_registry() {
if [ -n "$DOCKER_REGISTRY" ]; then
print_info "登录到 $DOCKER_REGISTRY..."
# 优先使用环境变量,如果没有则交互式输入
if [ -n "$DOCKER_USERNAME" ] && [ -n "$DOCKER_PASSWORD" ]; then
echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" -u "$DOCKER_USERNAME" --password-stdin
else
docker login "$DOCKER_REGISTRY"
fi
fi
}
# 构建Docker镜像
build_image() {
local full_image_name="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:${TAG}"
print_info "构建镜像: $full_image_name"
# 构建参数
BUILD_ARGS=""
if [ -f ".env" ]; then
BUILD_ARGS="--env-file .env"
fi
docker build \
$BUILD_ARGS \
-t "$full_image_name" \
-f "$DOCKERFILE" \
"$CONTEXT"
# 同时打上latest标签
local latest_image="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:latest"
docker tag "$full_image_name" "$latest_image"
print_info "镜像构建成功: $full_image_name"
}
# 推送镜像
push_image() {
local full_image_name="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:${TAG}"
local latest_image="${DOCKER_REGISTRY:+$DOCKER_REGISTRY/}${IMAGE_NAME}:latest"
print_info "推送镜像: $full_image_name"
docker push "$full_image_name"
# 如果tag不是latest,也推送latest
if [ "$TAG" != "latest" ]; then
print_info "推送镜像: $latest_image"
docker push "$latest_image"
fi
print_info "镜像推送成功!"
}
# 清理本地镜像
cleanup() {
print_info "清理构建缓存..."
docker builder prune -f
}
# 使用说明
usage() {
echo "用法: $0 [选项]"
echo "选项:"
echo " -r, --registry URL Docker仓库地址"
echo " -n, --name NAME 镜像名称"
echo " -t, --tag TAG 镜像标签 (默认: git tag 或 latest)"
echo " -f, --file FILE Dockerfile路径 (默认: Dockerfile)"
echo " -c, --context DIR 构建上下文 (默认: .)"
echo " --no-clean 不清理构建缓存"
echo " -h, --help 显示帮助信息"
}
# 主函数
main() {
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
-r|--registry)
DOCKER_REGISTRY="$2"
shift 2
;;
-n|--name)
IMAGE_NAME="$2"
shift 2
;;
-t|--tag)
TAG="$2"
shift 2
;;
-f|--file)
DOCKERFILE="$2"
shift 2
;;
-c|--context)
CONTEXT="$2"
shift 2
;;
--no-clean)
NO_CLEAN=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
print_error "未知参数: $1"
usage
exit 1
;;
esac
done
check_dependencies
login_registry
build_image
push_image
if [ "$NO_CLEAN" != "true" ]; then
cleanup
fi
print_info "完成!"
}
main "$@"
Node.js项目打包上传到npm
#!/usr/bin/env node
// publish.js - 自动打包并上传到npm
const { execSync } = require('child_process');
const fs = require('fs-extra');
const path = require('path');
const readline = require('readline');
// 颜色输出
const colors = {
green: (msg) => `\x1b[32m${msg}\x1b[0m`,
red: (msg) => `\x1b[31m${msg}\x1b[0m`,
yellow: (msg) => `\x1b[33m${msg}\x1b[0m`,
blue: (msg) => `\x1b[34m${msg}\x1b[0m`
};
function runCommand(cmd, options = {}) {
console.log(colors.blue(`执行: ${cmd}`));
try {
execSync(cmd, { stdio: 'inherit', ...options });
} catch (error) {
console.error(colors.red(`命令执行失败: ${cmd}`));
process.exit(1);
}
}
async function confirmPrompt(message) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(`${message} (y/n) `, (answer) => {
rl.close();
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
});
});
}
async function publish() {
const args = process.argv.slice(2);
const isDryRun = args.includes('--dry-run');
const shouldBumpVersion = args.includes('--bump');
const registry = process.env.npm_config_registry || 'https://registry.npmjs.org';
console.log(colors.green('=== npm 包自动发布脚本 ==='));
console.log(`仓库地址: ${registry}`);
console.log(`模拟运行: ${isDryRun ? '是' : '否'}`);
console.log(''); // 空行
// 1. 检查是否有未提交的更改
try {
const status = execSync('git status --porcelain').toString();
if (status.trim()) {
console.log(colors.yellow('警告: 存在未提交的更改:'));
console.log(status);
if (!isDryRun) {
const answer = await confirmPrompt(colors.yellow('是否继续?'));
if (!answer) {
console.log('发布取消');
process.exit(0);
}
}
}
} catch (error) {
console.error(colors.red('Git检查失败'));
process.exit(1);
}
// 2. 更新版本号(可选)
if (shouldBumpVersion && !isDryRun) {
console.log(colors.green('更新版本号...'));
runCommand('npm version patch', { stdio: 'pipe' });
}
// 3. 清理和构建
console.log(colors.green('清理和构建...'));
// 清理
const dirsToClean = ['dist', 'build', '.next', 'out'];
dirsToClean.forEach(dir => {
if (fs.existsSync(dir)) {
fs.removeSync(dir);
console.log(`清理目录: ${dir}`);
}
});
// 运行构建
const packageJson = fs.readJsonSync('package.json');
if (packageJson.scripts.build) {
console.log(colors.green('运行构建脚本...'));
runCommand('npm run build');
} else {
console.log(colors.yellow('未找到构建脚本,跳过构建'));
}
// 4. 检查dist目录
const distDir = packageJson.main || packageJson.module || 'dist';
if (!fs.existsSync(distDir)) {
console.log(colors.yellow(`警告: ${distDir} 目录不存在`));
if (!isDryRun) {
const answer = await confirmPrompt('是否继续发布?');
if (!answer) {
console.log('发布取消');
process.exit(0);
}
}
}
// 5. 登录npm(如果需要)
try {
const whoami = execSync('npm whoami').toString().trim();
console.log(colors.green(`已登录: ${whoami}`));
} catch {
console.log(colors.yellow('未登录npm'));
if (!isDryRun) {
runCommand('npm login');
}
}
// 6. 发布
console.log(colors.green('发布到npm...'));
if (isDryRun) {
console.log(colors.yellow('模拟运行,不会实际发布'));
runCommand('npm pack --dry-run');
} else {
// 检查是否发布到私有仓库
const isPrivate = packageJson.private;
if (isPrivate) {
console.log(colors.yellow('这是私有包,将发布到私有仓库'));
}
runCommand('npm publish');
}
console.log(colors.green('\n=== 发布完成! ==='));
// 7. 创建git tag(可选)
if (!isDryRun) {
const tagVersion = execSync('node -e "console.log(require(\'./package.json\').version)"').toString().trim();
try {
execSync(`git tag v${tagVersion}`);
execSync('git push --tags');
console.log(colors.green(`已创建Git标签: v${tagVersion}`));
} catch (error) {
console.log(colors.yellow('Git标签创建失败,请手动创建'));
}
}
}
// 帮助信息
function showHelp() {
console.log(`
用法: node publish.js [选项]
选项:
--dry-run 模拟运行,不实际发布
--bump 自动更新版本号 (patch)
--help 显示帮助信息
`);
}
if (process.argv.includes('--help')) {
showHelp();
} else {
publish().catch(console.error);
}
通用部署脚本(适用于任何类型的项目)
#!/bin/bash
# deploy.sh - 通用部署脚本
set -e
# 配置
PROJECT_NAME="my-project"
BUILD_DIR="./build"
DEPLOY_SERVER="user@example.com"
DEPLOY_PATH="/var/www/${PROJECT_NAME}"
BACKUP_PATH="/var/www/backups/${PROJECT_NAME}"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
# 检查必要的命令
check_requirements() {
local commands=("git" "tar" "ssh" "rsync")
for cmd in "${commands[@]}"; do
if ! command -v $cmd &> /dev/null; then
log_error "需要安装 $cmd"
exit 1
fi
done
}
# 获取 Git 信息
get_git_info() {
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
DEPLOY_DATE=$(date +%Y%m%d_%H%M%S)
VERSION="${BRANCH_NAME}-${COMMIT_HASH}-${DEPLOY_DATE}"
log_info "部署版本: $VERSION"
}
# 预处理:运行测试
run_tests() {
if [ -f "package.json" ] && grep -q '"test"' package.json; then
log_step "运行测试..."
npm test || {
log_error "测试失败"
exit 1
}
fi
if [ -f "pytest.ini" ] || [ -f "setup.cfg" ] && grep -q "\[tool:pytest\]" setup.cfg; then
log_step "运行Python测试..."
python -m pytest || {
log_error "测试失败"
exit 1
}
fi
}
# 构建项目
build_project() {
log_step "构建项目..."
# 根据项目类型选择构建方式
if [ -f "package.json" ]; then
log_info "检测到 Node.js 项目"
npm ci --production
if grep -q '"build"' package.json; then
npm run build
fi
elif [ -f "Makefile" ]; then
log_info "使用 Makefile 构建"
make build
elif [ -f "Dockerfile" ]; then
log_info "检测到 Docker 项目"
docker build -t "${PROJECT_NAME}:${VERSION}" .
fi
log_info "构建完成"
}
# 创建部署包
create_deploy_package() {
log_step "创建部署包..."
DEPLOY_PACKAGE="/tmp/${PROJECT_NAME}-${VERSION}.tar.gz"
# 排除不需要的文件
tar -czf "$DEPLOY_PACKAGE" \
--exclude=".git" \
--exclude="node_modules" \
--exclude="__pycache__" \
--exclude=".env" \
--exclude="*.log" \
.
log_info "部署包创建成功: $DEPLOY_PACKAGE"
}
# 备份当前版本
backup_current() {
log_step "备份当前版本..."
ssh "$DEPLOY_SERVER" "mkdir -p $BACKUP_PATH"
local backup_name="${DEPLOY_DATE}_${COMMIT_HASH}"
ssh "$DEPLOY_SERVER" "if [ -d $DEPLOY_PATH/current ]; then \
cp -r $DEPLOY_PATH/current $BACKUP_PATH/$backup_name; \
log_info '备份完成'; \
fi"
}
# 部署到服务器
deploy_to_server() {
log_step "部署到服务器..."
# 上传部署包
scp "$DEPLOY_PACKAGE" "$DEPLOY_SERVER:/tmp/"
# 在服务器上解压并更新
ssh "$DEPLOY_SERVER" << EOF
set -e
# 创建部署目录
mkdir -p $DEPLOY_PATH/releases/$VERSION
# 解压部署包
tar -xzf /tmp/${PROJECT_NAME}-${VERSION}.tar.gz \
-C $DEPLOY_PATH/releases/$VERSION
# 创建或更新符号链接
ln -sfn $DEPLOY_PATH/releases/$VERSION $DEPLOY_PATH/current
# 复制配置文件
if [ -f $DEPLOY_PATH/shared/.env ]; then
cp $DEPLOY_PATH/shared/.env $DEPLOY_PATH/current/
fi
# 重启服务
if command -v systemctl &> /dev/null; then
systemctl restart $PROJECT_NAME || true
elif command -v supervisorctl &> /dev/null; then
supervisorctl restart $PROJECT_NAME || true
elif [ -f $DEPLOY_PATH/current/restart.sh ]; then
cd $DEPLOY_PATH/current && ./restart.sh
fi
# 清理旧的部署包
rm /tmp/${PROJECT_NAME}-${VERSION}.tar.gz
echo "部署完成"
EOF
log_info "部署到服务器成功"
}
# 健康检查
health_check() {
log_step "健康检查..."
# 等待服务启动
sleep 5
if curl -f http://$DEPLOY_SERVER/health &> /dev/null; then
log_info "健康检查通过"
else
log_warn "健康检查失败,可能需要手动检查"
fi
}
# 清理旧的发布版本
cleanup_old_releases() {
log_step "清理旧版本..."
# 保留最近的5个版本
ssh "$DEPLOY_SERVER" "cd $DEPLOY_PATH/releases && \
ls -t | tail -n +6 | xargs -I {} rm -rf {} 2>/dev/null || true"
# 清理本地临时文件
rm -f "$DEPLOY_PACKAGE"
log_info "清理完成"
}
# 回滚到上一个版本
rollback() {
log_step "执行回滚..."
ssh "$DEPLOY_SERVER" << EOF
local previous=\$(ls -t $DEPLOY_PATH/releases/ | sed -n '2p')
if [ -n "\$previous" ]; then
ln -sfn $DEPLOY_PATH/releases/\$previous $DEPLOY_PATH/current
echo "回滚到版本: \$previous"
else
echo "没有可回滚的版本"
exit 1
fi
EOF
health_check
}
# 主函数
main() {
local action="${1:-deploy}"
echo "================================================"
echo " 部署脚本 - $PROJECT_NAME"
echo " 操作: $action"
echo "================================================"
check_requirements
case "$action" in
deploy)
get_git_info
run_tests || true # 允许测试失败继续
build_project
create_deploy_package
backup_current
deploy_to_server
health_check
cleanup_old_releases
log_info "部署完成!"
;;
rollback)
rollback
;;
status)
ssh "$DEPLOY_SERVER" "cd $DEPLOY_PATH && ls -l current && echo '部署路径:' && readlink -f current"
;;
*)
echo "用法: $0 {deploy|rollback|status}"
exit 1
;;
esac
}
main "$@"
使用方法
Python脚本:
# 上传到PyPI python publish.py # 上传到TestPyPI python publish.py --test # 自动更新patch版本 python publish.py --bump patch
Docker脚本:
# 基本使用 chmod +x docker-build-and-push.sh ./docker-build-and-push.sh # 指定自定义参数 ./docker-build-and-push.sh -r myregistry.com -n myapp -t v1.0.0 # 模拟运行 ./docker-build-and-push.sh --dry-run
npm脚本:
# 发布到npm node publish.js # 模拟运行 node publish.js --dry-run # 自动更新版本号 node publish.js --bump
通用部署脚本:
# 部署 chmod +x deploy.sh ./deploy.sh deploy # 回滚 ./deploy.sh rollback # 查看部署状态 ./deploy.sh status
配置建议
-
环境变量管理:
# .env 文件 DOCKER_USERNAME=your_username DOCKER_PASSWORD=your_password NPM_TOKEN=your_npm_token
-
GitHub Actions CI/CD: 创建
.github/workflows/deploy.yml配置文件 -
使用release-it自动化:
npm install -g release-it release-it --dry-run
这些脚本可以根据你的具体需求进行修改和扩展,记得先在一个测试环境中验证脚本的正确性,然后再用于生产环境。