从本地到云端:前端 dist 包直传与 Git 版本智能生成实践

213 阅读3分钟

在现代前端开发中,版本控制和自动化部署是提高团队效率和产品质量的关键因素。本文将介绍一个强大的脚本,它结合了版本管理、更新日志生成和自动化部署功能,为前端开发团队提供了一个完整的解决方案。

1. 概述

我们将探讨一个基于 Node.js 的脚本,它使用了 zxsemverfs-extrainquirer 等库来实现以下功能:

  • 自动版本号递增
  • 基于 Git 提交记录生成更新日志
  • 交互式用户界面
  • 自动化构建和部署

2. 技术栈

  • Node.js
  • zx:用于编写更易读的 shell 脚本
  • semver:语义化版本控制
  • fs-extra:增强的文件系统操作
  • inquirer:命令行用户界面
  • Git:版本控制系统

3. 脚本结构

脚本主要分为以下几个部分:

  1. 配置和导入
  2. Git 提交记录获取
  3. 更新日志管理
  4. 版本更新逻辑
  5. 部署流程

让我们详细探讨每个部分。

4. 配置和导入


#!/usr/bin/env zx
import semver from 'semver'
import fs from 'fs-extra'
import path from 'path'
import inquirer from 'inquirer'
import { execSync } from 'child_process'

$.verbose = true

// 定义相关信息
const serverHost = '49.232.232.209'
const username = 'root'
const remoteDir = '/www/wwwroot/zh-h5.crofys.cn'
const localDir = './dist'
const packageJsonPath = path.resolve('./package.json')
const changelogPath = path.resolve('./CHANGELOG.md')

这部分设置了脚本的基本配置,包括服务器信息、本地和远程目录路径等。使用 zx 可以让我们在 JavaScript 中更方便地执行 shell 命令。

5. Git 提交记录获取

async function getLatestGitCommits(count = 10) {
  const gitLog = execSync(`git log -${count} --pretty=format:"%h %s"`, { encoding: 'utf-8' })
  return gitLog.split('\n').map(line => {
    const [hash, ...messageParts] = line.split(' ')
    return { hash, message: messageParts.join(' ') }
  })
}

这个函数使用 git log 命令获取最近的 Git 提交记录,并将其格式化为一个包含哈希和消息的对象数组。这为生成更新日志提供了基础。

6. 更新日志管理

async function getLastChangelogEntry() {
  const changelog = await fs.readFile(changelogPath, 'utf8').catch(() => '')
  const matches = changelog.match(/## \[\d+\.\d+\.\d+\] - \d{4}-\d{2}-\d{2}\n\n((?:- .+\n)+)/)
  if (matches && matches[1]) {
    return matches[1].split('\n').filter(line => line.trim()).map(line => line.replace(/^- /, ''))
  }
  return []
}

async function updateChangelog(version, content) {
  const date = new Date().toISOString().split('T')[0]
  const changelogEntry = `
## [${version}] - ${date}

${content.split('\n').map(line => `- ${line}`).join('\n')}
`

  let changelog = await fs.readFile(changelogPath, 'utf8').catch(() => '')
  if (!changelog.includes('# Changelog')) {
    changelog = '# Changelog\n\n' + changelog
  }

  changelog = changelog.replace('# Changelog\n', `# Changelog\n\n${changelogEntry}\n`)
  await fs.writeFile(changelogPath, changelog)
}

这两个函数负责管理 CHANGELOG.md 文件。getLastChangelogEntry 获取最后一次更新的内容,而 updateChangelog 则负责将新的更新内容添加到文件中。这确保了更新日志的一致性和可读性。

7. 版本更新逻辑

async function updateVersion() {
  const packageJson = await fs.readJson(packageJsonPath)
  const currentVersion = packageJson.version

  const { versionType } = await inquirer.prompt({
    type: 'list',
    name: 'versionType',
    message: '选择要升级的版本类型:',
    choices: ['patch', 'minor', 'major']
  })

  const newVersion = semver.inc(currentVersion, versionType)
  
  // ... 省略部分代码 ...

  const lastChangelogEntries = await getLastChangelogEntry()
  const recentCommits = await getLatestGitCommits()
  const newCommits = recentCommits.filter(commit => 
    !lastChangelogEntries.some(entry => entry.includes(commit.message))
  )

  const { selectedCommits } = await inquirer.prompt({
    type: 'checkbox',
    name: 'selectedCommits',
    message: '选择要包含在更新日志中的提交:',
    choices: newCommits.map(commit => ({
      name: `${commit.hash.slice(0, 7)} ${commit.message}`,
      value: commit.message
    }))
  })

  const updateContent = selectedCommits.join('\n')

  packageJson.version = newVersion
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 })
  
  await updateChangelog(newVersion, updateContent)
  
  console.log(`版本号已更新: ${currentVersion} -> ${newVersion}`)
  return newVersion
}

这个函数是脚本的核心,它处理版本更新的整个流程:

  1. 读取当前版本号
  2. 让用户选择要升级的版本类型(patch、minor 或 major)
  3. 计算新的版本号
  4. 获取最近的 Git 提交记录
  5. 过滤出新的提交(不在上一次更新日志中的提交)
  6. 让用户选择要包含在新的更新日志中的提交
  7. 更新 package.json 中的版本号
  8. 更新 CHANGELOG.md 文件

这个过程既自动化了版本控制,又保留了人工干预的灵活性,确保了版本更新的准确性和相关性。

8. 部署流程

async function deployToRemote() {
  console.log('开始部署...')

  const newVersion = await updateVersion()

  await $`npm run build`

  if (!await fs.pathExists(localDir)) {
    console.error('错误: 本地 dist 目录不存在!')
    process.exit(1)
  }

  console.log('清除远程目录...')
  await $`ssh ${username}@${serverHost} "rm -rf ${remoteDir}/*"`

  console.log('确保远程目录存在...')
  await $`ssh ${username}@${serverHost} "mkdir -p ${remoteDir}"`

  console.log('上传文件到远程服务器...')
  await $`scp -r ${localDir}/* ${username}@${serverHost}:${remoteDir}`

  console.log('创建版本文件...')
  await $`ssh ${username}@${serverHost} "echo ${newVersion} > ${remoteDir}/version.txt"`

  console.log(`部署完成!新版本: ${newVersion}`)
}

deployToRemote().catch(err => {
  console.error('部署过程中发生错误:', err)
  process.exit(1)
})

部署函数 deployToRemote 执行以下步骤:

  1. 更新版本
  2. 构建项目
  3. 清理远程目录
  4. 上传新的构建文件
  5. 在远程服务器上创建版本文件

这个过程自动化了整个部署流程,大大减少了人为错误的可能性。

9. 最佳实践和注意事项

  • 安全性:确保服务器访问凭证得到妥善保护,不要在代码中硬编码敏感信息。
  • 错误处理:脚本包含了基本的错误处理,但在生产环境中可能需要更健壮的错误处理机制。
  • 配置管理:考虑使用配置文件来管理服务器信息和其他可变参数,以增加灵活性。
  • 持续集成:这个脚本可以很容易地集成到 CI/CD 管道中,进一步自动化开发流程。

10. 结论

这个脚本为前端开发团队提供了一个强大的工具,自动化了版本控制、更新日志生成和部署过程。通过结合 Git 操作、语义化版本控制和交互式命令行界面,它不仅提高了效率,还确保了版本管理的一致性和准确性。

随着项目的发展,可以进一步扩展这个脚本,例如添加自动化测试、性能检查或者与项目管理工具的集成。持续改进这样的自动化工具,将极大地提升团队的生产力和产品质量。

需要全部代码私信我