Skip to content

Commit e58729e

Browse files
authored
Enhance output messages and add success indicators
1 parent 4c76227 commit e58729e

1 file changed

Lines changed: 202 additions & 69 deletions

File tree

deepstudio/deepstudio/private-install-v2.ps1

Lines changed: 202 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,50 @@ $DefaultRegistry = [System.Text.Encoding]::UTF8.GetString([System.Convert]::From
3333
# ---------------------------
3434
# Helpers
3535
# ---------------------------
36-
function Info([string]$msg) { Write-Host $msg }
37-
function Warn([string]$msg) { Write-Host $msg }
38-
function Fail([string]$msg) { Write-Host $msg }
36+
function Info([string]$msg) { Write-Host " $msg" -ForegroundColor Cyan }
37+
function Warn([string]$msg) { Write-Host "$msg" -ForegroundColor Yellow }
38+
function Fail([string]$msg) { Write-Host "$msg" -ForegroundColor Red }
39+
function Success([string]$msg) { Write-Host "$msg" -ForegroundColor Green }
40+
function Dim([string]$msg) { Write-Host " $msg" -ForegroundColor DarkGray }
41+
42+
function Show-Banner {
43+
$lines = @(
44+
" ____ ____ _ _ _ "
45+
" | _ \ ___ ___ _ __ / ___|| |_ _ _ __| (_) ___ "
46+
" | | | |/ _ \/ _ \ '_ \\___ \| __| | | |/ _`` | |/ _ \ "
47+
" | |_| | __/ __/ |_) |___) | |_| |_| | (_| | | (_) |"
48+
" |____/ \___|\___| .__/|____/ \__|\__,_|\__,_|_|\___/ "
49+
" |_| I n s t a l l e r v2 "
50+
)
51+
$colors = @("Red", "Yellow", "Green", "Cyan", "Blue", "Magenta")
52+
Write-Host ""
53+
for ($i = 0; $i -lt $lines.Count; $i++) {
54+
Write-Host $lines[$i] -ForegroundColor $colors[$i % $colors.Count]
55+
}
56+
Write-Host ""
57+
}
58+
59+
function Mask-Url([string]$url) {
60+
if ([string]::IsNullOrEmpty($url)) { return "<empty>" }
61+
try {
62+
$u = [Uri]$url
63+
$host_ = $u.Host
64+
if ($host_.Length -gt 8) {
65+
$host_ = $host_.Substring(0, 4) + "****" + $host_.Substring($host_.Length - 4)
66+
}
67+
return "$($u.Scheme)://$host_/****"
68+
} catch {
69+
if ($url.Length -le 10) { return ("*" * $url.Length) }
70+
return $url.Substring(0, 5) + "****" + $url.Substring($url.Length - 4)
71+
}
72+
}
3973

4074
function Require-Npm {
4175
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
4276
Write-Host ""
43-
Write-Host "Node.js / npm not found."
44-
Write-Host "Please install Node.js first:"
45-
Write-Host "https://nodejs.org/en/download"
77+
Fail "Node.js / npm not found."
78+
Info "Please install Node.js first:"
79+
Info "👉 https://nodejs.org/en/download"
4680
Write-Host ""
4781
exit 1
4882
}
@@ -153,34 +187,104 @@ function Run-NpmViaCmd([string]$cmdLine) {
153187
}
154188
}
155189

190+
# ---------------------------
191+
# Azure CLI installation
192+
# ---------------------------
193+
function Install-AzureCli {
194+
# Attempt to install Azure CLI via winget
195+
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
196+
Warn "winget not found — cannot auto-install Azure CLI."
197+
return $false
198+
}
199+
200+
Info "📥 Installing Azure CLI via winget..."
201+
Write-Host ""
202+
try {
203+
& winget install --id Microsoft.AzureCLI --accept-source-agreements --accept-package-agreements
204+
if ($LASTEXITCODE -ne 0) {
205+
Warn "winget install returned exit code $LASTEXITCODE."
206+
return $false
207+
}
208+
# Refresh PATH so az is available in the current session
209+
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")
210+
if (Get-Command az -ErrorAction SilentlyContinue) {
211+
Success "Azure CLI installed successfully."
212+
return $true
213+
} else {
214+
Warn "Azure CLI installed but 'az' not found in PATH. You may need to restart your terminal."
215+
return $false
216+
}
217+
}
218+
catch {
219+
Warn "Failed to install Azure CLI: $($_.Exception.Message)"
220+
return $false
221+
}
222+
}
223+
156224
# ---------------------------
157225
# Azure CLI token acquisition
158226
# ---------------------------
159227
function Get-AzAccessToken {
160228
# Try to obtain a temporary access token via Azure CLI for Azure DevOps.
161229
# The resource ID 499b84ac-1321-427f-aa17-267ca6975798 is the well-known
162230
# resource identifier for Azure DevOps (Azure Artifacts / Packaging).
163-
if (-not (Get-Command az -ErrorAction SilentlyContinue)) {
164-
Info "Azure CLI (az) not found. Will fall back to manual PAT entry."
231+
$azAvailable = [bool](Get-Command az -ErrorAction SilentlyContinue)
232+
233+
if (-not $azAvailable) {
234+
Write-Host ""
235+
Write-Host " ┌─────────────────────────────────────────────────────┐" -ForegroundColor Yellow
236+
Write-Host " │ 🔧 Azure CLI (az) is not installed │" -ForegroundColor Yellow
237+
Write-Host " │ It is recommended for automatic token auth. │" -ForegroundColor Yellow
238+
Write-Host " └─────────────────────────────────────────────────────┘" -ForegroundColor Yellow
239+
Write-Host ""
240+
Write-Host " 📥 " -NoNewline -ForegroundColor Cyan
241+
$installChoice = Read-Host "Install Azure CLI now via winget? [Y/n]"
242+
if ([string]::IsNullOrWhiteSpace($installChoice) -or $installChoice -match '^[Yy]') {
243+
$installed = Install-AzureCli
244+
if ($installed) {
245+
$azAvailable = $true
246+
} else {
247+
Dim "Continuing without Azure CLI."
248+
}
249+
} else {
250+
Dim "Skipping Azure CLI installation."
251+
}
252+
}
253+
254+
if (-not $azAvailable) {
255+
Dim "Will fall back to manual PAT entry."
165256
return $null
166257
}
167258

168-
Info "Azure CLI found. Checking for existing az login session..."
259+
Info "🔍 Azure CLI found. Checking login session..."
169260

170261
try {
171262
$tokenJson = & az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query "accessToken" -o tsv 2>&1
172263
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($tokenJson)) {
173-
if ($VerboseInstall -and $tokenJson) { Info "az output: $tokenJson" }
174-
Info "No valid az login session found. Will fall back to manual PAT entry."
175-
return $null
264+
if ($VerboseInstall -and $tokenJson) { Dim "az output: $tokenJson" }
265+
Warn "No valid az login session found."
266+
Write-Host ""
267+
Info "🔐 Please log in to Azure to continue..."
268+
Write-Host ""
269+
& az login
270+
if ($LASTEXITCODE -ne 0) {
271+
Warn "az login failed — will fall back to manual PAT entry."
272+
return $null
273+
}
274+
# Retry after login
275+
$tokenJson = & az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query "accessToken" -o tsv 2>&1
276+
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($tokenJson)) {
277+
Warn "Still unable to get token after login — will fall back to manual PAT entry."
278+
return $null
279+
}
176280
}
177281
$token = $tokenJson.Trim()
178-
Info "Obtained temporary access token from Azure CLI session."
282+
Success "Obtained temporary access token from Azure CLI."
179283
return $token
180284
}
181285
catch {
182-
Info "Failed to get token from Azure CLI: $($_.Exception.Message)"
183-
Info "Will fall back to manual PAT entry."
286+
Warn "Failed to get token from Azure CLI: $($_.Exception.Message)"
287+
Dim "Will fall back to manual PAT entry."
184288
return $null
185289
}
186290
}
@@ -190,23 +294,39 @@ function Get-AzAccessToken {
190294
# ---------------------------
191295
Require-Npm
192296

193-
Info ""
194-
Info "=== DeepStudio npm installer (v2) ==="
195-
Info "Package: $PackageName@latest"
196-
Info ("VerboseInstall: " + $(if ($VerboseInstall) { "ON" } else { "OFF" }))
197-
Info ("DryRun: " + $(if ($DryRun) { "ON" } else { "OFF" }))
198-
Info ("LogToFile: " + $(if ($EnableLog) { "ON ($LogPath)" } else { "OFF" }))
199-
Info ""
200-
201-
# Get registry: env > default
297+
Show-Banner
298+
299+
Write-Host " ┌─────────────────────────────────────────┐" -ForegroundColor DarkCyan
300+
Write-Host " │ 📦 Package: " -NoNewline -ForegroundColor DarkCyan
301+
Write-Host "$PackageName@latest" -NoNewline -ForegroundColor White
302+
Write-Host "" -ForegroundColor DarkCyan
303+
Write-Host " │ 🔧 Verbose: " -NoNewline -ForegroundColor DarkCyan
304+
Write-Host $(if ($VerboseInstall) { "ON " } else { "OFF" }) -NoNewline -ForegroundColor $(if ($VerboseInstall) { "Green" } else { "DarkGray" })
305+
Write-Host "" -ForegroundColor DarkCyan
306+
Write-Host " │ 🧪 DryRun: " -NoNewline -ForegroundColor DarkCyan
307+
Write-Host $(if ($DryRun) { "ON " } else { "OFF" }) -NoNewline -ForegroundColor $(if ($DryRun) { "Yellow" } else { "DarkGray" })
308+
Write-Host "" -ForegroundColor DarkCyan
309+
Write-Host " │ 📝 LogFile: " -NoNewline -ForegroundColor DarkCyan
310+
Write-Host $(if ($EnableLog) { "ON " } else { "OFF" }) -NoNewline -ForegroundColor $(if ($EnableLog) { "Green" } else { "DarkGray" })
311+
Write-Host "" -ForegroundColor DarkCyan
312+
Write-Host " └─────────────────────────────────────────┘" -ForegroundColor DarkCyan
313+
Write-Host ""
314+
315+
# Get registry: env > construct from org name using default template
202316
$registryInput = $RegistryFromEnv
203317
if ([string]::IsNullOrWhiteSpace($registryInput)) {
204-
$registryInput = $DefaultRegistry
318+
Write-Host " 🏢 " -NoNewline -ForegroundColor Cyan
319+
$adoOrg = Read-Host "Enter ADO org name [microsoft]"
320+
if ([string]::IsNullOrWhiteSpace($adoOrg)) { $adoOrg = "microsoft" }
321+
# Construct registry URL from org name using the base64 template
322+
# Template: https://{org}.pkgs.visualstudio.com/OS/_packaging/DeepStudio/npm/registry/
323+
$registryInput = $DefaultRegistry -replace "microsoft\.pkgs", "$adoOrg.pkgs"
324+
Dim "Using org: $adoOrg"
205325
}
206326
$registry = Ensure-Registry $registryInput
207327

208-
Info "Registry: $registry"
209-
Info ""
328+
Dim "Registry: $(Mask-Url $registry)"
329+
Write-Host ""
210330

211331
# derive auth prefix (npmrc style)
212332
$uri = [Uri]$registry
@@ -216,24 +336,27 @@ $authPrefix = "//" + $uri.Host + $uri.AbsolutePath.TrimEnd("/") + "/"
216336
$pat = Get-AzAccessToken
217337

218338
if ([string]::IsNullOrWhiteSpace($pat)) {
219-
Info ""
220-
Info "Manual PAT required. You can create one at:"
221-
Info " https://dev.azure.com/ > User Settings > Personal Access Tokens"
222-
Info " Scope: Packaging > Read"
223-
Info ""
224-
225-
$patRaw = Read-Secure "Enter Azure DevOps PAT (Packaging:Read)"
339+
Write-Host ""
340+
Write-Host " ┌─────────────────────────────────────────────────────┐" -ForegroundColor Yellow
341+
Write-Host " │ 🔑 Manual PAT required │" -ForegroundColor Yellow
342+
Write-Host " │ Create one at: │" -ForegroundColor Yellow
343+
Write-Host " │ https://dev.azure.com/ > User Settings > PATs │" -ForegroundColor Yellow
344+
Write-Host " │ Scope: Packaging > Read │" -ForegroundColor Yellow
345+
Write-Host " └─────────────────────────────────────────────────────┘" -ForegroundColor Yellow
346+
Write-Host ""
347+
348+
$patRaw = Read-Secure " 🔑 Enter Azure DevOps PAT (Packaging:Read)"
226349
if ([string]::IsNullOrWhiteSpace($patRaw)) { throw "PAT is empty." }
227350

228351
$pat = $patRaw.Trim()
229352
$patRaw = $null
230353
} else {
231-
Info "Using temporary token from Azure CLI (no PAT creation needed)."
354+
Success "Using temporary token from Azure CLI (no PAT creation needed)."
232355
}
233356

234-
Info ""
235-
Info ("Token (masked): " + (Mask-Token $pat))
236-
Info ""
357+
Write-Host ""
358+
Dim ("Token (masked): " + (Mask-Token $pat))
359+
Write-Host ""
237360

238361
# npm install args / loglevel
239362
$logLevel = if ($VerboseInstall) { "verbose" } else { "notice" }
@@ -248,16 +371,19 @@ if ($VerboseInstall) {
248371
}
249372

250373
try {
251-
Info "Preparing isolated npm config (ignores your existing .npmrc)..."
374+
Info "📄 Preparing isolated npm config..."
252375
Write-IsolatedNpmrc -path $tmpNpmrc -registry $registry -authPrefix $authPrefix -pat $pat
253376

254377
# Build command line (use cmd.exe to avoid PowerShell alias/function issues with npm)
255378
# IMPORTANT: Use --userconfig so npm only reads our isolated temp npmrc for this run.
256379
$installCmd = 'npm install -g "{0}@latest" --registry "{1}/" --loglevel {2} --userconfig "{3}"' -f $PackageName, $registry, $logLevel, $tmpNpmrc
257380

258-
Info ""
259-
Info ("Command: " + $installCmd)
260-
Info ""
381+
if ($VerboseInstall) {
382+
Dim ("Command: " + $installCmd)
383+
}
384+
Write-Host ""
385+
Info "📦 Installing $PackageName@latest ..."
386+
Write-Host ""
261387

262388
Run-NpmViaCmd $installCmd
263389

@@ -267,53 +393,60 @@ try {
267393
Run-NpmViaCmd $verifyCmd
268394
}
269395

270-
Info ""
271-
Info "✅ Installed $PackageName@latest successfully."
272-
Info ""
396+
Write-Host ""
397+
Write-Host " ┌─────────────────────────────────────────────────┐" -ForegroundColor Green
398+
Write-Host " │ 🎉 $PackageName@latest installed successfully! │" -ForegroundColor Green
399+
Write-Host " └─────────────────────────────────────────────────┘" -ForegroundColor Green
400+
Write-Host ""
273401

274402
# Ask user whether to start deepstudio-server
403+
Write-Host " 🚀 " -NoNewline -ForegroundColor Magenta
275404
$startChoice = Read-Host "Start $PackageName now? [Y/n]"
276405
if ([string]::IsNullOrWhiteSpace($startChoice) -or $startChoice -match '^[Yy]') {
277-
Info ""
278-
Info "🚀 Launching $PackageName (press Ctrl+C to stop)..."
279-
Info ""
406+
Write-Host ""
407+
Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Magenta
408+
Success "Launching $PackageName (press Ctrl+C to stop)..."
409+
Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Magenta
410+
Write-Host ""
280411
if ($DryRun) {
281-
Info "DRYRUN: would run $PackageName"
412+
Dim "DRYRUN: would run $PackageName"
282413
} else {
283414
& $PackageName
284415
}
285416
} else {
286-
Info ""
417+
Write-Host ""
287418
Info "You can start it later by running: $PackageName"
288419
}
289420
}
290421
catch {
291-
Info ""
292-
Fail "❌ Installation failed."
422+
Write-Host ""
423+
Write-Host " ┌─────────────────────────────────────────────────┐" -ForegroundColor Red
424+
Write-Host " │ ❌ Installation failed │" -ForegroundColor Red
425+
Write-Host " └─────────────────────────────────────────────────┘" -ForegroundColor Red
293426
Fail ("Error: " + $_.Exception.Message)
294427

295-
Warn ""
428+
Write-Host ""
296429
Warn "Common causes for 401/403:"
297-
Warn " - Azure CLI token expired (try: az login)"
298-
Warn " - PAT missing Packaging:Read scope"
299-
Warn " - PAT pasted with extra whitespace (we trimmed, but double-check the masked value)"
300-
Warn " - Wrong registry URL (must be the npm/registry endpoint)"
301-
Warn " - No permission to the Azure Artifacts feed"
302-
Warn " - Corporate proxy/SSL interception issues"
430+
Dim " Azure CLI token expired (try: az login)"
431+
Dim " PAT missing Packaging:Read scope"
432+
Dim " PAT pasted with extra whitespace"
433+
Dim " Wrong registry URL"
434+
Dim " No permission to the Azure Artifacts feed"
435+
Dim " Corporate proxy/SSL interception issues"
303436

304437
if ($VerboseInstall) {
305-
Info ""
306-
Info "---- Full exception ----"
307-
Info $_.Exception.ToString()
308-
Info "------------------------"
438+
Write-Host ""
439+
Dim "---- Full exception ----"
440+
Dim $_.Exception.ToString()
441+
Dim "------------------------"
309442
} else {
310-
Warn ""
443+
Write-Host ""
311444
Warn "Tip: re-run with verbose:"
312-
Warn " `$env:DEEPSTUDIO_VERBOSE='1'; `$env:DEEPSTUDIO_LOG='1'; irm <url> | iex"
445+
Dim " `$env:DEEPSTUDIO_VERBOSE='1'; `$env:DEEPSTUDIO_LOG='1'; irm <url> | iex"
313446
}
314447

315448
if ($EnableLog -and -not $DryRun) {
316-
Warn ""
449+
Write-Host ""
317450
Warn "Log saved to: $LogPath"
318451
}
319452

@@ -328,7 +461,7 @@ finally {
328461
Remove-Item Env:\NPM_CONFIG_LOGLEVEL -ErrorAction SilentlyContinue
329462
Remove-Item Env:\NPM_CONFIG_PROGRESS -ErrorAction SilentlyContinue
330463
}
331-
Info ""
332-
Info "Cleanup complete."
333-
Info ""
464+
Write-Host ""
465+
Success "Cleanup complete."
466+
Write-Host ""
334467
}

0 commit comments

Comments
 (0)