Add Windows start and update script
This commit is contained in:
+180
@@ -0,0 +1,180 @@
|
||||
#Requires -Version 5.1
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Position = 0)]
|
||||
[ValidateSet('start', 'update')]
|
||||
[string]$Action = 'start',
|
||||
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$ListenAddress = '0.0.0.0',
|
||||
|
||||
[ValidateRange(1, 65535)]
|
||||
[int]$Port = 9000,
|
||||
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$DataDir = 'C:\ProgramData\WebTools\data',
|
||||
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$Remote = 'origin',
|
||||
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$Branch = 'main',
|
||||
|
||||
[switch]$NoStart
|
||||
)
|
||||
|
||||
Set-StrictMode -Version 3.0
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$projectRoot = $PSScriptRoot
|
||||
$serverEntry = Join-Path $projectRoot 'server\index.mjs'
|
||||
$distIndex = Join-Path $projectRoot 'dist\index.html'
|
||||
|
||||
function Resolve-Application {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name
|
||||
)
|
||||
|
||||
$command = Get-Command $Name -CommandType Application -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $command) {
|
||||
throw "Required command '$Name' was not found in PATH."
|
||||
}
|
||||
|
||||
return $command.Source
|
||||
}
|
||||
|
||||
function Invoke-NativeCommand {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Executable,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string[]]$Arguments
|
||||
)
|
||||
|
||||
& $Executable @Arguments
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0) {
|
||||
throw "Command '$Executable $($Arguments -join ' ')' failed with exit code $exitCode."
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-ProductionBuild {
|
||||
if (-not (Test-Path -LiteralPath $serverEntry -PathType Leaf)) {
|
||||
throw "Server entry was not found: $serverEntry"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $distIndex -PathType Leaf)) {
|
||||
throw "Production build was not found: $distIndex. Run '.\web-tools.ps1 update' first."
|
||||
}
|
||||
}
|
||||
|
||||
function Update-WebTools {
|
||||
$git = Resolve-Application 'git.exe'
|
||||
$npm = Resolve-Application 'npm.cmd'
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
$insideWorkTree = @(& $git rev-parse --is-inside-work-tree)
|
||||
if ($LASTEXITCODE -ne 0 -or ($insideWorkTree -join '').Trim() -ne 'true') {
|
||||
throw "Project directory is not a Git work tree: $projectRoot"
|
||||
}
|
||||
|
||||
$statusLines = @(& $git status --porcelain --untracked-files=normal)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Unable to inspect the Git work tree."
|
||||
}
|
||||
if ($statusLines.Count -gt 0) {
|
||||
throw "The Git work tree contains local changes. Update was stopped to avoid overwriting them:`n$($statusLines -join "`n")"
|
||||
}
|
||||
|
||||
$currentBranchOutput = @(& $git branch --show-current)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Unable to determine the current Git branch."
|
||||
}
|
||||
$currentBranch = ($currentBranchOutput -join '').Trim()
|
||||
if ($currentBranch -ne $Branch) {
|
||||
throw "Current branch is '$currentBranch'; expected '$Branch'."
|
||||
}
|
||||
|
||||
Write-Host "Updating $Remote/$Branch..."
|
||||
Invoke-NativeCommand $git @('pull', '--ff-only', $Remote, $Branch)
|
||||
|
||||
$previousNodeEnv = [Environment]::GetEnvironmentVariable('NODE_ENV', 'Process')
|
||||
try {
|
||||
[Environment]::SetEnvironmentVariable('NODE_ENV', $null, 'Process')
|
||||
|
||||
Write-Host 'Installing locked dependencies (npm ci replaces node_modules automatically)...'
|
||||
Invoke-NativeCommand $npm @('ci', '--include=dev')
|
||||
|
||||
Write-Host 'Building the production application...'
|
||||
Invoke-NativeCommand $npm @('run', 'build')
|
||||
}
|
||||
finally {
|
||||
[Environment]::SetEnvironmentVariable('NODE_ENV', $previousNodeEnv, 'Process')
|
||||
}
|
||||
|
||||
Assert-ProductionBuild
|
||||
Write-Host 'Update and production build completed successfully.'
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Start-WebTools {
|
||||
Assert-ProductionBuild
|
||||
$node = Resolve-Application 'node.exe'
|
||||
$dataDirectory = (New-Item -ItemType Directory -Force -Path $DataDir).FullName
|
||||
|
||||
$previousEnvironment = @{
|
||||
NODE_ENV = [Environment]::GetEnvironmentVariable('NODE_ENV', 'Process')
|
||||
HOST = [Environment]::GetEnvironmentVariable('HOST', 'Process')
|
||||
PORT = [Environment]::GetEnvironmentVariable('PORT', 'Process')
|
||||
DATA_DIR = [Environment]::GetEnvironmentVariable('DATA_DIR', 'Process')
|
||||
}
|
||||
|
||||
try {
|
||||
[Environment]::SetEnvironmentVariable('NODE_ENV', 'production', 'Process')
|
||||
[Environment]::SetEnvironmentVariable('HOST', $ListenAddress, 'Process')
|
||||
[Environment]::SetEnvironmentVariable('PORT', $Port.ToString(), 'Process')
|
||||
[Environment]::SetEnvironmentVariable('DATA_DIR', $dataDirectory, 'Process')
|
||||
|
||||
Write-Host "Starting Web Tools on http://${ListenAddress}:$Port"
|
||||
Write-Host "Navigation config: $(Join-Path $dataDirectory 'navigation.json')"
|
||||
Write-Host 'Press Ctrl+C to stop.'
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
& $node $serverEntry
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0) {
|
||||
throw "Web Tools exited with code $exitCode."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
finally {
|
||||
foreach ($name in $previousEnvironment.Keys) {
|
||||
[Environment]::SetEnvironmentVariable($name, $previousEnvironment[$name], 'Process')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($Action -eq 'start' -and $NoStart) {
|
||||
throw "-NoStart can only be used with the 'update' action."
|
||||
}
|
||||
|
||||
if ($Action -eq 'update') {
|
||||
Update-WebTools
|
||||
if ($NoStart) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Start-WebTools
|
||||
Reference in New Issue
Block a user