如何编写自动打包并上传脚本

wen 实用脚本 2

本文目录导读:

如何编写自动打包并上传脚本

  1. Python项目打包上传到PyPI
  2. Docker镜像打包上传
  3. Node.js项目打包上传到npm
  4. 通用部署脚本(适用于任何类型的项目)
  5. 使用方法
  6. 配置建议

我来帮你编写一个自动打包并上传的脚本,以下是几种常见场景的示例:

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

配置建议

  1. 环境变量管理

    # .env 文件
    DOCKER_USERNAME=your_username
    DOCKER_PASSWORD=your_password
    NPM_TOKEN=your_npm_token
  2. GitHub Actions CI/CD: 创建 .github/workflows/deploy.yml 配置文件

  3. 使用release-it自动化

    npm install -g release-it
    release-it --dry-run

这些脚本可以根据你的具体需求进行修改和扩展,记得先在一个测试环境中验证脚本的正确性,然后再用于生产环境。

抱歉,评论功能暂时关闭!