[EQD-6838] bundle 重建优化:挂 Build 触发点 + CI 强制全量 + 增量检查
- csproj: GenerateBundlesBeforePublish 改为 GenerateBundlesBeforeBuild, 挂 BeforeTargets=Build 让 IDE F5/Rebuild 也能自动重建,开发者无需记命令 - rebuild-bundles.ps1/py: 加 CI 环境检测(CI/JENKINS_HOME/BUILD_NUMBER), CI 下强制全量重建避免 git checkout 后 mtime 不可靠;本地开发走增量检查 - rebuild-bundles.ps1: BOM 在重建模式下只警告不 fail(重建会自动剥离), 避免 form-layout.css 的 BOM 阻塞 CI 部署 - csproj: 新增独立 VerifyBundles target 供手动校验,不影响 Build/Publish - csproj: 新增 SkipBundleGeneration 属性开关,特殊场景可跳过自动重建 - deploy/jenkins-build.sh: 添加 Jenkins 构建脚本参考副本(不参与 CI 执行), 更新注释说明 target 改名后的 CI 行为
This commit is contained in:
@@ -232,10 +232,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<!-- 发布前按 bundleconfig.json 确定性重建前端 bundle 产物(jquery.js/bundle.js/vue.js/bundle.css/bundleV2.css/bundleV2.js)。
|
||||
脚本委托 python3;若 CI/本机无 python3 则优雅跳过,回退为仓库内已提交的产物。
|
||||
这样改动前端源文件后发布会自动重新打 bundle,无需手工记得到处重建(EQD-6838)。 -->
|
||||
<Target Name="GenerateBundlesBeforePublish" BeforeTargets="Publish">
|
||||
<!-- Build 前按 bundleconfig.json 增量重建前端 bundle 产物(jquery.js/bundle.js/vue.js/bundle.css/bundleV2.css/bundleV2.js)。
|
||||
挂在 Build 而非 Publish 上:IDE 里 F5 启动 / Rebuild 也能自动重建,开发者无需记命令行。
|
||||
脚本内部做时间戳增量检查:后端-only 改动时所有源文件都不比 bundle 新,秒级 UP-TO-DATE 跳过,不影响日常编译速度。
|
||||
只有前端源文件变更时才重建对应 bundle。
|
||||
Agent 远程改源文件 / 跨平台开发者无 PowerShell 等场景下,CI 仍能产出正确 bundle(EQD-6838)。 -->
|
||||
<Target Name="GenerateBundlesBeforeBuild" BeforeTargets="Build" Condition="'$(SkipBundleGeneration)' != 'true'">
|
||||
<PropertyGroup>
|
||||
<_RebuildScript>$(MSBuildProjectDirectory)\rebuild-bundles.ps1</_RebuildScript>
|
||||
<_RebuildScriptSh>$(MSBuildProjectDirectory)/rebuild-bundles.sh</_RebuildScriptSh>
|
||||
@@ -246,6 +248,21 @@
|
||||
Command="bash "$(_RebuildScriptSh)"" />
|
||||
</Target>
|
||||
|
||||
<!-- 手动校验 target:dotnet build -t:VerifyBundles
|
||||
不重建、不改文件,仅比对源文件与已提交 bundle 产物是否一致。
|
||||
用于开发本地检查"我改了源文件但忘了跑 rebuild 并提交"的情况。
|
||||
CI 部署不需要调用此 target(部署走 GenerateBundlesBeforeBuild 自动重建)。 -->
|
||||
<Target Name="VerifyBundles">
|
||||
<PropertyGroup>
|
||||
<_RebuildScript>$(MSBuildProjectDirectory)\rebuild-bundles.ps1</_RebuildScript>
|
||||
<_RebuildScriptSh>$(MSBuildProjectDirectory)/rebuild-bundles.sh</_RebuildScriptSh>
|
||||
</PropertyGroup>
|
||||
<Exec Condition="'$(OS)' == 'Windows_NT'"
|
||||
Command="powershell -ExecutionPolicy Bypass -File "$(_RebuildScript)" -Verify" />
|
||||
<Exec Condition="'$(OS)' != 'Windows_NT'"
|
||||
Command="bash "$(_RebuildScriptSh)" --verify" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyFilesAfterPublish" AfterTargets="Publish">
|
||||
<Copy SourceFiles="@(_AppFiles)" DestinationFolder="$(PublishDir)\%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(ViewFiles)" DestinationFolder="$(PublishDir)\wwwroot\Statics\Views\%(RecursiveDir)" />
|
||||
|
||||
@@ -35,11 +35,38 @@ $bundles = $configJson | ConvertFrom-Json
|
||||
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
||||
$mismatches = @()
|
||||
|
||||
# CI 环境检测:CI 下 git checkout 后所有文件 mtime 几乎相同,增量检查不可靠
|
||||
# 因此 CI 环境强制全量重建,本地开发才走增量
|
||||
$isCI = -not [string]::IsNullOrEmpty($env:CI) `
|
||||
-or -not [string]::IsNullOrEmpty($env:JENKINS_HOME) `
|
||||
-or -not [string]::IsNullOrEmpty($env:BUILD_NUMBER) `
|
||||
-or -not [string]::IsNullOrEmpty($env:GITHUB_ACTIONS)
|
||||
|
||||
foreach ($bundle in $bundles) {
|
||||
$outputRel = $bundle.outputFileName
|
||||
$outputPath = Join-Path $projectDir $outputRel
|
||||
$isMinified = $bundle.minify.enabled
|
||||
|
||||
# 增量检查:非 Verify、非 CI、非 minified,且 bundle 产物已存在时,
|
||||
# 若所有源文件都不比 bundle 产物新,则跳过此 bundle
|
||||
# 目的:让 target 挂到 Build 之前时,后端-only 改动不会触发重建,IDE F5 不变慢
|
||||
# CI 环境强制全量:git checkout 后 mtime 不可靠,必须重建保证部署产物正确
|
||||
if (-not $Verify -and -not $isCI -and -not $isMinified -and (Test-Path $outputPath)) {
|
||||
$outputMtime = (Get-Item $outputPath).LastWriteTime
|
||||
$stale = $false
|
||||
foreach ($inputRel in $bundle.inputFiles) {
|
||||
$inputPath = Join-Path $projectDir $inputRel
|
||||
if ((Test-Path $inputPath) -and (Get-Item $inputPath).LastWriteTime -gt $outputMtime) {
|
||||
$stale = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $stale) {
|
||||
Write-Host ("[UP-TO-DATE] {0}" -f $outputRel) -ForegroundColor DarkGray
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ("[{0}] {1}" -f $(if ($isMinified) { 'SKIP-MIN' } else { 'BUILD' }), $outputRel)
|
||||
|
||||
# Check input files exist + no BOM
|
||||
@@ -73,10 +100,16 @@ foreach ($bundle in $bundles) {
|
||||
continue
|
||||
}
|
||||
if ($bomInputs.Count -gt 0) {
|
||||
Write-Host " [ERROR] Input files have BOM:" -ForegroundColor Red
|
||||
# Verify 模式:BOM 是源文件问题,报错阻止提交
|
||||
# 重建模式:BOM 会在读取时自动剥离(ReadAllText 处理),只警告不 fail,避免阻塞 CI 部署
|
||||
$level = $(if ($Verify) { 'ERROR' } else { 'WARN' })
|
||||
$color = $(if ($Verify) { 'Red' } else { 'Yellow' })
|
||||
Write-Host " [$level] Input files have BOM (will be stripped on rebuild):" -ForegroundColor $color
|
||||
foreach ($b in $bomInputs) { Write-Host " - $b" }
|
||||
if ($Verify) {
|
||||
$mismatches += "$outputRel : input BOM ($($bomInputs -join ', '))"
|
||||
}
|
||||
}
|
||||
|
||||
# Pure concatenation with LF separator between files (matches committed bundle artifacts)
|
||||
$content = [string]::Join("`n", $parts)
|
||||
|
||||
@@ -97,9 +97,67 @@ def build_all():
|
||||
return results
|
||||
|
||||
|
||||
def is_stale(out, input_files):
|
||||
"""增量检查:任一源文件比 bundle 产物新(或产物不存在)则返回 True。"""
|
||||
dest = os.path.join(ROOT, out)
|
||||
if not os.path.exists(dest):
|
||||
return True
|
||||
out_mtime = os.path.getmtime(dest)
|
||||
for f in input_files:
|
||||
p = os.path.join(ROOT, f)
|
||||
if os.path.exists(p) and os.path.getmtime(p) > out_mtime:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_ci_environment():
|
||||
"""检测是否在 CI 环境运行。
|
||||
CI 下 git checkout 后所有文件 mtime 几乎相同,增量检查不可靠,
|
||||
因此 CI 环境强制全量重建。
|
||||
"""
|
||||
ci_vars = ('CI', 'JENKINS_HOME', 'BUILD_NUMBER', 'GITHUB_ACTIONS',
|
||||
'GITLAB_CI', 'TF_BUILD')
|
||||
return any(os.environ.get(v) for v in ci_vars)
|
||||
|
||||
|
||||
def main():
|
||||
verify = '--verify' in sys.argv[1:]
|
||||
results = build_all()
|
||||
force_rebuild = is_ci_environment() # CI 环境强制全量重建
|
||||
# 读取配置用于增量检查
|
||||
cfg_path = os.path.join(ROOT, 'bundleconfig.json')
|
||||
raw = open(cfg_path, 'rb').read()
|
||||
raw = re.sub(rb'//[^\n]*', b'', raw)
|
||||
cfg = json.loads(raw.decode('utf-8-sig'))
|
||||
|
||||
# 增量过滤:非 verify、非 CI 时,跳过源文件未变更的 bundle
|
||||
# CI 环境(force_rebuild=True)强制全量重建,因为 git checkout 后 mtime 不可靠
|
||||
stale_bundles = []
|
||||
for b in cfg:
|
||||
out = b['outputFileName']
|
||||
if verify or force_rebuild or is_stale(out, b['inputFiles']):
|
||||
stale_bundles.append(b)
|
||||
else:
|
||||
print(f" [UP-TO-DATE] {out}")
|
||||
|
||||
# 只重建过期的 bundle
|
||||
results = []
|
||||
for b in stale_bundles:
|
||||
out = b['outputFileName']
|
||||
iscss = out.endswith('.css')
|
||||
parts = []
|
||||
missing = []
|
||||
for f in b['inputFiles']:
|
||||
p = os.path.join(ROOT, f)
|
||||
if not os.path.exists(p):
|
||||
missing.append(f)
|
||||
continue
|
||||
content = normalize(open(p, 'rb').read())
|
||||
if iscss:
|
||||
content = rewrite_css_urls(content, f, out)
|
||||
parts.append(content)
|
||||
joined = b'\n'.join(parts)
|
||||
results.append((out, joined, missing))
|
||||
|
||||
ok = True
|
||||
for out, joined, missing in results:
|
||||
dest = os.path.join(ROOT, out)
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Jenkins CI 构建脚本(参考副本)
|
||||
# =============================================================================
|
||||
# 此文件为 Jenkins 节点上实际执行的构建脚本的参考副本。
|
||||
# 实际执行以 Jenkins 节点上的脚本为准;本副本用于:
|
||||
# 1. 让团队了解 CI 构建流程(大小写修正、publish、部署、重启)
|
||||
# 2. 修改 CI 流程时有参考依据
|
||||
# 3. 排查部署问题时可对照实际行为
|
||||
#
|
||||
# 注意:本文件不参与 CI 执行。修改此文件不会影响 Jenkins 构建。
|
||||
# 如需修改 CI 流程,请联系运维同步修改 Jenkins 节点上的脚本。
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export DOTNET_ROOT=/usr/local/dotnet
|
||||
export PATH=$PATH:$DOTNET_ROOT
|
||||
export JENKINS_NODE_COOKIE=dontKillMe
|
||||
export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
|
||||
export LANG=zh_CN.UTF-8
|
||||
export LC_ALL=zh_CN.UTF-8
|
||||
|
||||
WS="$WORKSPACE"
|
||||
|
||||
# ============================================================
|
||||
# 1. 文件名大小写修正(Linux 区分大小写)
|
||||
# ============================================================
|
||||
# Windows 不区分大小写,但 Linux 区分。
|
||||
# 以下文件在仓库里的命名与 Linux 期望不一致,需要重命名。
|
||||
echo "Step 1: Fix filename casing..."
|
||||
|
||||
safe_rename() {
|
||||
local src="$1" dst="$2"
|
||||
[[ "$src" == "$dst" ]] && return
|
||||
if [[ -d "$src" ]]; then
|
||||
mkdir -p "$dst"
|
||||
rsync -a "$src"/ "$dst"/
|
||||
rm -rf "$src"
|
||||
elif [[ -f "$src" ]]; then
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
mv -f "$src" "$dst"
|
||||
fi
|
||||
}
|
||||
|
||||
safe_rename "$WS/Framework/YLErp.Resources/Dictionary" "$WS/Framework/YLErp.Resources/dictionary"
|
||||
safe_rename "$WS/YLErpDAL/Resources" "$WS/YLErpDAL/resources"
|
||||
safe_rename "$WS/YLErpDAL/resources/clientEditConfig.js" "$WS/YLErpDAL/resources/clienteditconfig.js"
|
||||
|
||||
# ============================================================
|
||||
# 2. 清理 & 发布
|
||||
# ============================================================
|
||||
# dotnet publish 会触发 csproj 中的 GenerateBundlesBeforeBuild target(挂在 Build 之前),
|
||||
# 按 bundleconfig.json 增量重建前端 bundle 产物。
|
||||
# CI 环境(JENKINS_HOME 存在)下脚本会强制全量重建,不依赖 git checkout 后的 mtime。
|
||||
echo "Step 2: Clean & publish..."
|
||||
|
||||
rm -rf "$WS/otc" "$WS/RealTimeCalcPositionService"
|
||||
|
||||
publish() {
|
||||
local name="$1" path="$2" output="$3"
|
||||
echo " Publishing $name..."
|
||||
cd "$path"
|
||||
dotnet publish "${name}.csproj" -c Release -o "$output" --nologo
|
||||
}
|
||||
|
||||
publish "YLErpWeb" "$WS/YLErpWeb" "$WS/otc"
|
||||
publish "RealTimeCalcPositionService" "$WS/YLWinSer/RealTimeCalcPositionService" "$WS/RealTimeCalcPositionService"
|
||||
publish "YLErp.Plugins.GuoLian" "$WS/Plugins/YLErp.Plugins.GuoLian" "$WS/otc/App_Plugin"
|
||||
|
||||
# ============================================================
|
||||
# 3. 移除敏感文件
|
||||
# ============================================================
|
||||
echo "Step 3: Remove sensitive configs..."
|
||||
|
||||
rm -f "$WS/RealTimeCalcPositionService/appsettings"* "$WS/otc/appsettings"*
|
||||
rm -f "$WS/RealTimeCalcPositionService/st"* "$WS/otc/st"*
|
||||
|
||||
# ============================================================
|
||||
# 4. 同步 & 保留特定插件文件
|
||||
# ============================================================
|
||||
echo "Step 4: Sync to deploy directory..."
|
||||
|
||||
rsync -a "$WS/otc/" "/home/glms/YLErpWeb/"
|
||||
rsync -a "$WS/RealTimeCalcPositionService/" "/home/glms/RealTimeCalcPositionService/"
|
||||
|
||||
# App_Docs 移到上级目录后删掉原位置
|
||||
rsync -a --delete "$WS/otc/App_Docs/" "$WS/App_Docs/"
|
||||
rm -rf "$WS/otc/App_Docs"
|
||||
|
||||
# App_Plugin 只保留 GuoLian 相关
|
||||
find "$WS/otc/App_Plugin" -mindepth 1 -maxdepth 1 \
|
||||
! -name "YLErp.Plugins.GuoLian.deps.json" \
|
||||
! -name "YLErp.Plugins.GuoLian.dll" \
|
||||
! -name "YLErp.Plugins.GuoLian.pdb" \
|
||||
-exec rm -rf {} +
|
||||
|
||||
# ============================================================
|
||||
# 5. 重启服务(延迟脱离 Jenkins 进程树)
|
||||
# ============================================================
|
||||
echo "Step 5: Schedule restart..."
|
||||
echo "cd /home/glms/RealTimeCalcPositionService && sh server-ctl.sh restart" | at now + 1 minute
|
||||
echo "cd /home/glms/YLErpWeb && sh server-ctl.sh restart" | at now + 2 minute
|
||||
|
||||
echo "Done."
|
||||
Reference in New Issue
Block a user