192 lines
8.5 KiB
PowerShell
192 lines
8.5 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Save-and-run launcher for a ShopDB dev workstation (stable or edge).
|
|
|
|
.DESCRIPTION
|
|
Download this one file and run it. It clones (or updates) the repo at the
|
|
channel branch into a local path, then hands off to scripts/dev-bootstrap.ps1
|
|
for the real dependency-check / build / up work.
|
|
|
|
ge-aero/shopdb-flask is a PRIVATE repo, but this needs no token pasting: the
|
|
clone uses a plain https URL and Git Credential Manager supplies your stored
|
|
GitHub credential automatically - the same way `git clone` works in the wiki
|
|
DEVELOPMENT-SETUP procedure. If you have ever cloned this repo or signed in to
|
|
GitHub in Visual Studio / VS Code on this box, GCM already has what it needs.
|
|
|
|
This is a downloaded FILE, not a web `iex` one-liner, precisely because a
|
|
private repo cannot be fetched anonymously from raw.githubusercontent.com. A
|
|
saved script sidesteps that entirely - only the git operations need auth, and
|
|
GCM handles those.
|
|
|
|
.PARAMETER Channel
|
|
'stable' (the newest release tag) or 'edge' (main, the trunk). Defaults to 'stable'.
|
|
|
|
.PARAMETER TargetDir
|
|
Where to clone. Defaults to %USERPROFILE%\shopdb-dev, a separate working copy
|
|
from any repo you already have open.
|
|
|
|
.EXAMPLE
|
|
# Bleeding edge:
|
|
powershell -ExecutionPolicy Bypass -File .\Start-ShopDBDev.ps1 -Channel edge
|
|
|
|
.EXAMPLE
|
|
# Stable:
|
|
powershell -ExecutionPolicy Bypass -File .\Start-ShopDBDev.ps1 -Channel stable
|
|
|
|
.NOTES
|
|
ASCII only, no emoji or smart punctuation, per repo style. PowerShell 5.1+.
|
|
Needs Git (with Credential Manager, the Git-for-Windows default);
|
|
dev-bootstrap.ps1 offers to winget-install anything else that is missing.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[ValidateSet('stable', 'edge')]
|
|
[string]$Channel = 'stable',
|
|
|
|
[string]$TargetDir
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# Reset leftovers from an interrupted or timed-out prior run so the retry starts
|
|
# clean. Proxy bottlenecks can kill a build mid-flight, leaving orphaned
|
|
# containers holding the channel ports, dangling build cache from the partial
|
|
# build, and sometimes a half-written clone. This is scoped to the channel and
|
|
# is non-destructive to data: the compose down keeps named volumes (the DB), and
|
|
# only dangling build cache is pruned - never tagged images or volumes.
|
|
function Clear-InterruptedRun {
|
|
param(
|
|
[Parameter(Mandatory)][string]$Project,
|
|
[Parameter(Mandatory)][string]$Target
|
|
)
|
|
|
|
if (Get-Command docker -ErrorAction SilentlyContinue) {
|
|
# docker/compose write warnings to stderr (e.g. "No resource found to
|
|
# remove"), which would trip $ErrorActionPreference='Stop' and abort the
|
|
# whole run. These cleanup steps are best-effort, so soften the preference
|
|
# and swallow all streams for their duration.
|
|
$previous = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
try {
|
|
# Only try compose down when the engine is actually responding, otherwise
|
|
# skip quietly - a fresh box may not have Docker started yet.
|
|
docker info *> $null
|
|
if ($LASTEXITCODE -eq 0) {
|
|
Write-Host "==> Cleaning up any interrupted '$Project' stack" -ForegroundColor Cyan
|
|
# --remove-orphans clears containers left by a killed build; volumes are
|
|
# intentionally kept so the DB survives a retry.
|
|
docker compose -p $Project down --remove-orphans *> $null
|
|
# Reclaim only dangling build cache from failed/partial builds.
|
|
docker builder prune -f *> $null
|
|
}
|
|
}
|
|
finally {
|
|
$ErrorActionPreference = $previous
|
|
}
|
|
}
|
|
|
|
# A directory that exists but has no valid .git is a broken/partial clone -
|
|
# USUALLY. It is also what -TargetDir pointed at somebody's Documents folder
|
|
# looks like, and this ran Remove-Item -Recurse -Force on it without asking.
|
|
# For a script distributed as "save it anywhere and run it", a mistyped
|
|
# -TargetDir must not be able to delete a user's files.
|
|
#
|
|
# Only reclaim a directory this script can show it owns: empty, or carrying
|
|
# the marker written after a successful clone. Anything else stops.
|
|
if ((Test-Path $Target) -and -not (Test-Path (Join-Path $Target '.git'))) {
|
|
$ours = Test-Path (Join-Path $Target $script:CloneMarker)
|
|
$empty = -not (Get-ChildItem -LiteralPath $Target -Force |
|
|
Select-Object -First 1)
|
|
if ($ours -or $empty) {
|
|
Write-Host "==> Removing incomplete clone at $Target" -ForegroundColor Cyan
|
|
Remove-Item -Path $Target -Recurse -Force
|
|
}
|
|
else {
|
|
throw ("$Target already exists, is not empty, and was not created " +
|
|
"by this launcher. Refusing to delete it. Pass -TargetDir " +
|
|
'pointing somewhere else, or remove it yourself if you are sure.')
|
|
}
|
|
}
|
|
|
|
# A clone created by an elevated (Administrator) run is owned by
|
|
# BUILTIN\Administrators; a later non-elevated run then trips git's
|
|
# "detected dubious ownership" guard and every git op fails with exit 128.
|
|
# That stale clone is effectively unusable for this user, so detect the
|
|
# condition and remove it here for a clean re-clone rather than surfacing a
|
|
# misleading auth error later.
|
|
if (Test-Path (Join-Path $Target '.git')) {
|
|
$previous = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
try { $probe = git -C $Target rev-parse --is-inside-work-tree 2>&1 } catch { $probe = "$_" }
|
|
finally { $ErrorActionPreference = $previous }
|
|
if ($probe -match 'dubious ownership') {
|
|
# Same rule as above: a .git directory proves this is a clone, but
|
|
# not that it is OUR clone, and re-cloning discards whatever is in
|
|
# it. Only reclaim one this launcher created.
|
|
if (Test-Path (Join-Path $Target $script:CloneMarker)) {
|
|
Write-Host "==> Removing clone with mismatched ownership at $Target (was it created from an elevated prompt?)" -ForegroundColor Cyan
|
|
Remove-Item -Path $Target -Recurse -Force
|
|
}
|
|
else {
|
|
throw ("The git clone at $Target has mismatched ownership and " +
|
|
'was not created by this launcher, so it is not being ' +
|
|
'deleted. Remove it yourself, or pass -TargetDir elsewhere.')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Plain https URL. No token in the URL: Git Credential Manager injects the
|
|
# stored GitHub credential for github.com, so clone/fetch authenticate against
|
|
# the private repo without anything sensitive touching this script or disk.
|
|
$repoUrl = 'https://github.com/ge-aero/shopdb-flask.git'
|
|
# Written into a clone this launcher made, so a later run can tell its own
|
|
# working directory apart from one that happens to sit at the same path.
|
|
$script:CloneMarker = '.shopdb-launcher'
|
|
# Channels follow how this repo ships: a release is a TAG, the trunk is main.
|
|
# Upstream this read 'dev-be' for edge, which is that repo's trunk branch and
|
|
# does not exist here - the launcher would have cloned a missing branch.
|
|
$branch = 'main'
|
|
if ($Channel -ne 'edge') {
|
|
# Newest release tag, so 'stable' means a thing that was released rather
|
|
# than whatever the trunk happens to be today.
|
|
$tag = (git ls-remote --tags --refs --sort='-v:refname' $repoUrl 'v*' |
|
|
Select-Object -First 1) -replace '.*refs/tags/', ''
|
|
if ($tag) { $branch = $tag }
|
|
else { Write-Warning "No v* tag found; falling back to main for 'stable'." }
|
|
}
|
|
$target = if ($TargetDir) { $TargetDir } else { Join-Path $env:USERPROFILE 'shopdb-dev' }
|
|
|
|
Write-Host "ShopDB dev launcher" -ForegroundColor White
|
|
Write-Host " Channel : $Channel ($branch)" -ForegroundColor White
|
|
Write-Host " Target : $target" -ForegroundColor White
|
|
|
|
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
|
throw 'git is required. Install Git for Windows (winget install Git.Git) and re-run.'
|
|
}
|
|
|
|
$project = if ($Channel -eq 'edge') { 'shopdb-edge' } else { 'shopdb-stable' }
|
|
Clear-InterruptedRun -Project $project -Target $target
|
|
|
|
if (Test-Path (Join-Path $target '.git')) {
|
|
Write-Host "==> Updating existing clone" -ForegroundColor Cyan
|
|
git -C $target fetch origin $branch
|
|
if ($LASTEXITCODE -ne 0) { throw "git fetch failed ($LASTEXITCODE). If this is an auth error, clone the repo once in Visual Studio / VS Code so Credential Manager stores your GitHub login." }
|
|
git -C $target checkout $branch
|
|
if ($LASTEXITCODE -ne 0) { throw "git checkout failed ($LASTEXITCODE)." }
|
|
git -C $target pull --ff-only origin $branch
|
|
if ($LASTEXITCODE -ne 0) { throw "git pull failed ($LASTEXITCODE)." }
|
|
} else {
|
|
Write-Host "==> Cloning $branch" -ForegroundColor Cyan
|
|
git clone --branch $branch $repoUrl $target
|
|
if ($LASTEXITCODE -ne 0) { throw "git clone failed ($LASTEXITCODE). If this is an auth error, sign in to GitHub once in Visual Studio / VS Code (or run 'git clone $repoUrl' manually) so Credential Manager stores your login, then re-run." }
|
|
}
|
|
|
|
$bootstrap = Join-Path $target 'scripts\dev-bootstrap.ps1'
|
|
if (-not (Test-Path $bootstrap)) {
|
|
throw "dev-bootstrap.ps1 not found at $bootstrap. Is the '$branch' branch correct?"
|
|
}
|
|
|
|
Write-Host "==> Handing off to dev-bootstrap.ps1" -ForegroundColor Cyan
|
|
& $bootstrap -Channel $Channel
|