Skip to content

Commit f38ba4b

Browse files
authored
Merge pull request #555 from Chris0Jeky/pkg/534-build-script
pkg: add release build script (#534)
2 parents aedc778 + ed9e14a commit f38ba4b

File tree

3 files changed

+403
-0
lines changed

3 files changed

+403
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Backend (.NET)
22
backend/src/*/bin/
33
backend/src/*/obj/
4+
# Generated SPA static files (produced by build-release.sh / build-release.ps1)
5+
backend/src/Taskdeck.Api/wwwroot/
46
backend/tests/*/bin/
57
backend/tests/*/obj/
68
backend/*.user

scripts/build-release.ps1

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#Requires -Version 5.1
2+
<#
3+
.SYNOPSIS
4+
Unified release build: npm run build -> copy dist/ to wwwroot/ -> dotnet publish
5+
6+
.DESCRIPTION
7+
Builds the Taskdeck Vue SPA, copies the output to the ASP.NET Core wwwroot/
8+
directory, then produces a self-contained single-file executable for the
9+
target platform.
10+
11+
.PARAMETER Rid
12+
.NET Runtime Identifier. Defaults to win-x64 on Windows.
13+
Supported values: win-x64 linux-x64 osx-x64 osx-arm64
14+
15+
.EXAMPLE
16+
.\scripts\build-release.ps1 # defaults to win-x64
17+
.\scripts\build-release.ps1 -Rid linux-x64 # cross-compile for Linux
18+
.\scripts\build-release.ps1 -Rid osx-arm64 # cross-compile for macOS ARM
19+
20+
.NOTES
21+
Requires: Node.js 24.x, npm, and the .NET 8 SDK on PATH.
22+
#>
23+
24+
[CmdletBinding()]
25+
param(
26+
[ValidateSet("win-x64", "linux-x64", "osx-x64", "osx-arm64")]
27+
[string]$Rid = "win-x64"
28+
)
29+
30+
Set-StrictMode -Version Latest
31+
$ErrorActionPreference = "Stop"
32+
33+
# ---------------------------------------------------------------------------
34+
# Constants
35+
# ---------------------------------------------------------------------------
36+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
37+
$RepoRoot = Split-Path -Parent $ScriptDir
38+
39+
$FrontendDir = Join-Path $RepoRoot "frontend/taskdeck-web"
40+
$FrontendDist = Join-Path $FrontendDir "dist"
41+
42+
$ApiProject = Join-Path $RepoRoot "backend/src/Taskdeck.Api/Taskdeck.Api.csproj"
43+
# NOTE: PKG-01 (#533) must be merged before UseStaticFiles / wwwroot serving is configured
44+
# in the .NET API (Program.cs / PipelineConfiguration.cs). Until that PR lands, the published
45+
# binary will NOT serve the SPA — it will return 404 for the frontend routes. Do not ship
46+
# a release artifact built from main until PKG-01 is merged.
47+
$Wwwroot = Join-Path $RepoRoot "backend/src/Taskdeck.Api/wwwroot"
48+
49+
$OutputBase = Join-Path $RepoRoot "artifacts\publish"
50+
$OutputDir = Join-Path $OutputBase $Rid
51+
52+
# ---------------------------------------------------------------------------
53+
# Helpers
54+
# ---------------------------------------------------------------------------
55+
function Write-Step { param([string]$Msg) Write-Host "[build-release] $Msg" -ForegroundColor Cyan }
56+
function Write-Warn { param([string]$Msg) Write-Warning "[build-release] $Msg" }
57+
function Write-Fatal { param([string]$Msg) Write-Error "[build-release] FATAL: $Msg"; exit 1 }
58+
59+
# ---------------------------------------------------------------------------
60+
# Dependency checks
61+
# ---------------------------------------------------------------------------
62+
function Assert-Dependencies {
63+
$missing = 0
64+
foreach ($cmd in @("node", "npm", "dotnet")) {
65+
if (-not (Get-Command $cmd -ErrorAction SilentlyContinue)) {
66+
Write-Warn "Required tool not found on PATH: $cmd"
67+
$missing++
68+
}
69+
}
70+
if ($missing -gt 0) {
71+
Write-Fatal "$missing required tool(s) not found. Install Node.js 24.x and the .NET 8 SDK before running this script."
72+
}
73+
74+
# Node version guard (must be 24.x)
75+
try {
76+
$nodeVersion = node -e "process.stdout.write(String(process.versions.node.split('.')[0]))" 2>$null
77+
$nodeMajor = [int]$nodeVersion
78+
} catch {
79+
$nodeMajor = 0
80+
}
81+
if ($nodeMajor -lt 24) {
82+
Write-Warn "Node.js 24.x is required; found $(node --version). Continuing, but the build may fail."
83+
}
84+
}
85+
86+
# ---------------------------------------------------------------------------
87+
# Step 1 - Frontend build
88+
# ---------------------------------------------------------------------------
89+
function Build-Frontend {
90+
Write-Step "Step 1/3 - Building Vue SPA (npm run build)..."
91+
92+
if (-not (Test-Path $FrontendDir)) {
93+
Write-Fatal "Frontend directory not found: $FrontendDir"
94+
}
95+
96+
$nodeModules = Join-Path $FrontendDir "node_modules"
97+
if (-not (Test-Path $nodeModules)) {
98+
Write-Step "node_modules not found - running npm install..."
99+
& npm install --prefix $FrontendDir
100+
if ($LASTEXITCODE -ne 0) { Write-Fatal "npm install failed (exit $LASTEXITCODE)." }
101+
}
102+
103+
& npm run build --prefix $FrontendDir
104+
if ($LASTEXITCODE -ne 0) { Write-Fatal "npm run build failed (exit $LASTEXITCODE)." }
105+
106+
if (-not (Test-Path $FrontendDist)) {
107+
Write-Fatal "Expected dist/ directory not produced at: $FrontendDist"
108+
}
109+
Write-Step "Frontend build complete: $FrontendDist"
110+
}
111+
112+
# ---------------------------------------------------------------------------
113+
# Step 2 - Copy dist/ to wwwroot/
114+
# ---------------------------------------------------------------------------
115+
function Copy-ToWwwroot {
116+
Write-Step "Step 2/3 - Copying dist/ -> wwwroot/..."
117+
118+
# Wipe and recreate to avoid stale files accumulating across builds
119+
if (Test-Path $Wwwroot) {
120+
Remove-Item -Path $Wwwroot -Recurse -Force
121+
}
122+
New-Item -ItemType Directory -Path $Wwwroot -Force | Out-Null
123+
124+
Copy-Item -Path (Join-Path $FrontendDist '*') -Destination $Wwwroot -Recurse -Force
125+
Write-Step "Copied to wwwroot: $Wwwroot"
126+
}
127+
128+
# ---------------------------------------------------------------------------
129+
# Step 3 - dotnet publish
130+
# ---------------------------------------------------------------------------
131+
function Publish-Backend {
132+
Write-Step "Step 3/3 - Publishing .NET API (RID=$Rid)..."
133+
Write-Step "Output directory: $OutputDir"
134+
135+
if (-not (Test-Path $ApiProject)) {
136+
Write-Fatal "API project file not found: $ApiProject"
137+
}
138+
139+
# TRIM WARNING: PublishTrimmed=true can silently break reflection-heavy code paths
140+
# (EF Core migrations, ASP.NET DI conventions, System.Text.Json, SignalR).
141+
# Validate the trimmed artifact with a smoke test before shipping.
142+
& dotnet publish $ApiProject `
143+
-c Release `
144+
-r $Rid `
145+
--self-contained true `
146+
-p:PublishSingleFile=true `
147+
-p:PublishTrimmed=true `
148+
-p:TrimmerRootAssembly=Taskdeck.Api `
149+
-o $OutputDir
150+
151+
if ($LASTEXITCODE -ne 0) { Write-Fatal "dotnet publish failed (exit $LASTEXITCODE)." }
152+
153+
Write-Step "Publish complete: $OutputDir"
154+
}
155+
156+
# ---------------------------------------------------------------------------
157+
# Summary
158+
# ---------------------------------------------------------------------------
159+
function Write-Summary {
160+
Write-Step ""
161+
Write-Step "Build complete."
162+
Write-Step " RID : $Rid"
163+
Write-Step " Artifact : $OutputDir"
164+
165+
$exeName = if ($Rid -like "win-*") { "Taskdeck.Api.exe" } else { "Taskdeck.Api" }
166+
$exePath = Join-Path $OutputDir $exeName
167+
168+
if (Test-Path $exePath) {
169+
$sizeKB = [math]::Round((Get-Item $exePath).Length / 1KB)
170+
Write-Step " Executable : $exePath (~$sizeKB KB)"
171+
if ($sizeKB -gt 102400) {
172+
Write-Warn "Executable is larger than 100 MB (~$sizeKB KB). Consider reviewing trim settings."
173+
}
174+
}
175+
}
176+
177+
# ---------------------------------------------------------------------------
178+
# Main
179+
# ---------------------------------------------------------------------------
180+
Write-Step "=== Taskdeck release build ==="
181+
Write-Step "RID: $Rid"
182+
Write-Step "Repo root: $RepoRoot"
183+
184+
Assert-Dependencies
185+
Build-Frontend
186+
Copy-ToWwwroot
187+
Publish-Backend
188+
Write-Summary

0 commit comments

Comments
 (0)