Skip to content

Commit 13ef3e5

Browse files
authored
Enhance logging and update installer version
Added logging functionality and updated installer version to 2.6.
1 parent 1e36fd9 commit 13ef3e5

1 file changed

Lines changed: 212 additions & 45 deletions

File tree

deepstudio/deepstudio/private-install-v2.1.ps1

Lines changed: 212 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,20 @@ $LogPath = if ($env:DEEPSTUDIO_LOG_PATH) { $env:DEEPSTUDIO_LOG_PATH } else {
2626
Join-Path (Get-Location) ("deepstudio-install-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".log")
2727
}
2828

29+
# Installer version
30+
$InstallerVersion = "2.6"
31+
2932
# Default registry for DeepStudio (stored as base64)
3033
$DefaultRegistryB64 = "aHR0cHM6Ly9taWNyb3NvZnQucGtncy52aXN1YWxzdHVkaW8uY29tL09TL19wYWNrYWdpbmcvRGVlcFN0dWRpby9ucG0vcmVnaXN0cnkv"
3134
$DefaultRegistry = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($DefaultRegistryB64))
3235

3336
# Will be set later after registry is resolved
3437
$script:ResolvedRegistry = $null
3538

39+
# Rolling in-memory log buffer (last N lines) — dumped on error
40+
$script:LogBuffer = [System.Collections.Generic.Queue[string]]::new()
41+
$script:LogBufferMax = 100
42+
3643
# ---------------------------
3744
# Helpers
3845
# ---------------------------
@@ -42,14 +49,32 @@ function Fail([string]$msg) { Write-Host " ✖ $msg" -ForegroundColor Red }
4249
function Success([string]$msg) { Write-Host "$msg" -ForegroundColor Green }
4350
function Dim([string]$msg) { Write-Host " $msg" -ForegroundColor DarkGray }
4451

52+
function Add-ToLogBuffer([string]$line) {
53+
$script:LogBuffer.Enqueue($line)
54+
while ($script:LogBuffer.Count -gt $script:LogBufferMax) {
55+
[void]$script:LogBuffer.Dequeue()
56+
}
57+
}
58+
59+
function Dump-LogBuffer {
60+
if ($script:LogBuffer.Count -eq 0) { return }
61+
Write-Host ""
62+
Warn "Recent output (last $($script:LogBuffer.Count) lines):"
63+
Write-Host " ────────────────────────────────────────────────" -ForegroundColor DarkGray
64+
foreach ($line in $script:LogBuffer) {
65+
Dim $line
66+
}
67+
Write-Host " ────────────────────────────────────────────────" -ForegroundColor DarkGray
68+
}
69+
4570
function Show-Banner {
4671
$lines = @(
4772
" ____ ____ _ _ _ "
4873
" | _ \ ___ ___ _ __ / ___|| |_ _ _ __| (_) ___ "
4974
" | | | |/ _ \/ _ \ '_ \\___ \| __| | | |/ _`` | |/ _ \ "
5075
" | |_| | __/ __/ |_) |___) | |_| |_| | (_| | | (_) |"
5176
" |____/ \___|\___| .__/|____/ \__|\__,_|\__,_|_|\___/ "
52-
" |_| I n s t a l l e r v2.1 "
77+
" |_| I n s t a l l e r v$InstallerVersion "
5378
)
5479
$colors = @("Red", "Yellow", "Green", "Cyan", "Blue", "Magenta")
5580
Write-Host ""
@@ -237,12 +262,13 @@ function New-TempNpmrc {
237262
function Write-IsolatedNpmrc([string]$path, [string]$registry, [string]$authPrefix, [string]$pat) {
238263
$content = @"
239264
registry=$registry/
265+
always-auth=true
240266
${authPrefix}:_authToken=$pat
241267
"@
242268

243269
if ($DryRun) {
244270
Info "DRYRUN: write isolated npmrc to $path"
245-
Info ("DRYRUN: npmrc content (token masked):`nregistry=$registry/`n${authPrefix}:_authToken=$(Mask-Token $pat)")
271+
Info ("DRYRUN: npmrc content (token masked):`nregistry=$registry/`nalways-auth=true`n${authPrefix}:_authToken=$(Mask-Token $pat)")
246272
return
247273
}
248274

@@ -291,23 +317,97 @@ function Show-ProxyDiagnostics {
291317
}
292318
}
293319

320+
function Show-NpmConfigConflicts {
321+
Write-Host ""
322+
Info "🔎 Checking for conflicting npm configurations..."
323+
$conflicts = 0
324+
325+
# 1. Check NPM_CONFIG_REGISTRY env var (overrides --registry in some npm versions)
326+
$envRegistry = $env:NPM_CONFIG_REGISTRY
327+
if (-not [string]::IsNullOrWhiteSpace($envRegistry)) {
328+
$conflicts++
329+
Warn "NPM_CONFIG_REGISTRY env var is set: $envRegistry"
330+
Warn " This can override --registry flag. Will be cleared during install."
331+
}
332+
333+
# 2. Check for project-level .npmrc in cwd and parent directories
334+
$searchDir = Get-Location
335+
$projectNpmrcs = @()
336+
while ($null -ne $searchDir -and $searchDir.Path.Length -gt 3) {
337+
$candidate = Join-Path $searchDir.Path ".npmrc"
338+
if (Test-Path $candidate) {
339+
$projectNpmrcs += $candidate
340+
}
341+
$searchDir = Split-Path $searchDir.Path -Parent | Get-Item -ErrorAction SilentlyContinue
342+
}
343+
if ($projectNpmrcs.Count -gt 0) {
344+
foreach ($p in $projectNpmrcs) {
345+
$conflicts++
346+
Warn "Project .npmrc found: $p"
347+
# Check if it sets a registry
348+
$content = Get-Content $p -Raw -ErrorAction SilentlyContinue
349+
if ($content -match '(?m)^\s*registry\s*=') {
350+
Warn " ↳ Contains 'registry=' — this WILL override the install registry!"
351+
}
352+
if ($content -match '(?m)^\s*//.*:_auth') {
353+
Dim " ↳ Contains auth tokens"
354+
}
355+
}
356+
}
357+
358+
# 3. Check for global npmrc
359+
try {
360+
$globalNpmrc = & npm config get globalconfig 2>$null
361+
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($globalNpmrc) -and (Test-Path $globalNpmrc)) {
362+
$content = Get-Content $globalNpmrc -Raw -ErrorAction SilentlyContinue
363+
if ($content -match '(?m)^\s*registry\s*=') {
364+
$conflicts++
365+
Warn "Global .npmrc has 'registry=' set: $globalNpmrc"
366+
Warn " ↳ This can override the install registry!"
367+
}
368+
}
369+
} catch {}
370+
371+
# 4. Check the user-level npmrc for conflicting registry
372+
try {
373+
$userNpmrc = & npm config get userconfig 2>$null
374+
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($userNpmrc) -and (Test-Path $userNpmrc)) {
375+
$content = Get-Content $userNpmrc -Raw -ErrorAction SilentlyContinue
376+
if ($content -match '(?m)^\s*registry\s*=') {
377+
Dim "User .npmrc has 'registry=' set: $userNpmrc (will be overridden by --userconfig)"
378+
}
379+
}
380+
} catch {}
381+
382+
# 5. Check current effective registry
383+
$effectiveRegistry = Get-NpmConfigValue "registry"
384+
if (-not [string]::IsNullOrWhiteSpace($effectiveRegistry)) {
385+
Dim "Current effective registry: $effectiveRegistry"
386+
}
387+
388+
if ($conflicts -eq 0) {
389+
Success "No conflicting npm configurations found."
390+
} else {
391+
Write-Host ""
392+
Warn "$conflicts conflict(s) detected — the installer will attempt to override them."
393+
}
394+
395+
return $conflicts
396+
}
397+
294398
function Run-NpmViaCmd([string]$cmdLine) {
295399
if ($DryRun) {
296400
Info ("DRYRUN: " + $cmdLine)
297401
return
298402
}
299403

300-
if (-not $EnableLog) {
301-
& cmd.exe /d /s /c $cmdLine
302-
if ($LASTEXITCODE -ne 0) {
303-
throw "npm failed with exit code $LASTEXITCODE. (Tip: set DEEPSTUDIO_LOG=1 to capture full logs.)"
304-
}
305-
return
306-
}
404+
Add-ToLogBuffer "[cmd] $cmdLine"
307405

308-
Info "Logging enabled. Log file: $LogPath"
309-
Info ("Command via cmd.exe: " + $cmdLine)
406+
if ($EnableLog) {
407+
Info "Logging enabled. Log file: $LogPath"
408+
}
310409

410+
# Use streaming output so the user sees progress in real-time
311411
$psi = New-Object System.Diagnostics.ProcessStartInfo
312412
$psi.FileName = "cmd.exe"
313413
$psi.Arguments = "/d /s /c " + $cmdLine
@@ -316,38 +416,76 @@ function Run-NpmViaCmd([string]$cmdLine) {
316416
$psi.UseShellExecute = $false
317417
$psi.CreateNoWindow = $true
318418

419+
$stdoutLines = [System.Collections.Generic.List[string]]::new()
420+
$stderrLines = [System.Collections.Generic.List[string]]::new()
421+
319422
$p = New-Object System.Diagnostics.Process
320423
$p.StartInfo = $psi
321-
[void]$p.Start()
424+
$p.EnableRaisingEvents = $true
425+
426+
# Stream stdout line-by-line
427+
$outAction = {
428+
if ($null -ne $EventArgs.Data) {
429+
$Event.MessageData.OutLines.Add($EventArgs.Data)
430+
Write-Host $EventArgs.Data
431+
}
432+
}
433+
$errAction = {
434+
if ($null -ne $EventArgs.Data) {
435+
$Event.MessageData.ErrLines.Add($EventArgs.Data)
436+
Write-Host $EventArgs.Data -ForegroundColor DarkYellow
437+
}
438+
}
439+
440+
$msgData = [PSCustomObject]@{ OutLines = $stdoutLines; ErrLines = $stderrLines }
441+
$outEvent = Register-ObjectEvent -InputObject $p -EventName OutputDataReceived -Action $outAction -MessageData $msgData
442+
$errEvent = Register-ObjectEvent -InputObject $p -EventName ErrorDataReceived -Action $errAction -MessageData $msgData
322443

323-
$stdout = $p.StandardOutput.ReadToEnd()
324-
$stderr = $p.StandardError.ReadToEnd()
444+
[void]$p.Start()
445+
$p.BeginOutputReadLine()
446+
$p.BeginErrorReadLine()
325447
$p.WaitForExit()
326448

327-
if ($stdout) { Write-Host $stdout }
328-
if ($stderr) { Write-Host $stderr }
329-
330-
$content = @(
331-
"=== DeepStudio install log ==="
332-
"Time: $(Get-Date -Format o)"
333-
"Package: $PackageName@latest"
334-
"VerboseInstall: $VerboseInstall"
335-
"Registry: $($script:ResolvedRegistry)/"
336-
"Command: $cmdLine"
337-
""
338-
"---- STDOUT ----"
339-
$stdout
340-
""
341-
"---- STDERR ----"
342-
$stderr
343-
""
344-
"ExitCode: $($p.ExitCode)"
345-
) -join "`r`n"
346-
347-
Set-Content -Path $LogPath -Value $content -Encoding UTF8
449+
# Give events a moment to flush
450+
Start-Sleep -Milliseconds 200
451+
452+
Unregister-Event -SourceIdentifier $outEvent.Name
453+
Unregister-Event -SourceIdentifier $errEvent.Name
454+
455+
# Feed output into rolling in-memory buffer
456+
foreach ($line in $stdoutLines) {
457+
if ($line) { Add-ToLogBuffer $line }
458+
}
459+
foreach ($line in $stderrLines) {
460+
if ($line) { Add-ToLogBuffer "[ERR] $line" }
461+
}
462+
463+
# Write to log file if enabled
464+
if ($EnableLog) {
465+
$content = @(
466+
"=== DeepStudio install log ==="
467+
"Time: $(Get-Date -Format o)"
468+
"Package: $PackageName@latest"
469+
"VerboseInstall: $VerboseInstall"
470+
"Registry: $($script:ResolvedRegistry)/"
471+
"Command: $cmdLine"
472+
""
473+
"---- STDOUT ----"
474+
($stdoutLines -join "`r`n")
475+
""
476+
"---- STDERR ----"
477+
($stderrLines -join "`r`n")
478+
""
479+
"ExitCode: $($p.ExitCode)"
480+
) -join "`r`n"
481+
482+
Set-Content -Path $LogPath -Value $content -Encoding UTF8
483+
}
348484

349485
if ($p.ExitCode -ne 0) {
350-
throw "npm failed with exit code $($p.ExitCode). See log: $LogPath"
486+
$errMsg = "npm failed with exit code $($p.ExitCode)."
487+
if ($EnableLog) { $errMsg += " See log: $LogPath" }
488+
throw $errMsg
351489
}
352490
}
353491

@@ -471,15 +609,12 @@ Write-Host " └─────────────────────
471609
Write-Host ""
472610

473611
Show-ProxyDiagnostics
612+
$configConflicts = Show-NpmConfigConflicts
474613

475-
# Get registry
614+
# Get registry (always use default; org override only via DEEPSTUDIO_REGISTRY env var)
476615
$registryInput = $RegistryFromEnv
477616
if ([string]::IsNullOrWhiteSpace($registryInput)) {
478-
Write-Host " 🏢 " -NoNewline -ForegroundColor Cyan
479-
$adoOrg = Read-Host "Enter ADO org name [microsoft]"
480-
if ([string]::IsNullOrWhiteSpace($adoOrg)) { $adoOrg = "microsoft" }
481-
$registryInput = $DefaultRegistry -replace "microsoft\.pkgs", "$adoOrg.pkgs"
482-
Dim "Using org: $adoOrg"
617+
$registryInput = $DefaultRegistry
483618
}
484619
$registry = Ensure-Registry $registryInput
485620
$script:ResolvedRegistry = $registry
@@ -525,11 +660,22 @@ if ($VerboseInstall) {
525660
$env:NPM_CONFIG_PROGRESS = "false"
526661
}
527662

663+
# Save and clear env vars that can override --registry
664+
$script:SavedNpmConfigRegistry = $env:NPM_CONFIG_REGISTRY
665+
$env:NPM_CONFIG_REGISTRY = $null
666+
528667
try {
529668
Info "📄 Preparing isolated npm config..."
530669
Write-IsolatedNpmrc -path $tmpNpmrc -registry $registry -authPrefix $authPrefix -pat $pat
531670

532-
$installCmd = 'npm install -g "{0}@latest" --registry "{1}/" --loglevel {2} --userconfig "{3}"' -f $PackageName, $registry, $logLevel, $tmpNpmrc
671+
# --userconfig overrides ~/.npmrc, --globalconfig overrides {prefix}/etc/npmrc
672+
# NUL for globalconfig so npm doesn't see a conflicting global config
673+
# NOTE: --registry is intentionally omitted from the CLI flags.
674+
# It is already set inside the isolated npmrc. Passing it on the CLI as well
675+
# puts the registry at CLI precedence while _authToken stays at userconfig
676+
# precedence, and npm 11 may refuse to send userconfig auth for a CLI-level
677+
# registry. Keeping both in the same npmrc avoids this precedence split.
678+
$installCmd = 'npm install -g "{0}@latest" --loglevel {1} --userconfig "{2}" --globalconfig NUL' -f $PackageName, $logLevel, $tmpNpmrc
533679

534680
if ($VerboseInstall) {
535681
Dim ("Command: " + $installCmd)
@@ -541,7 +687,7 @@ try {
541687
Run-NpmViaCmd $installCmd
542688

543689
if (-not $DryRun) {
544-
$verifyCmd = 'npm list -g --depth=0 "{0}" --userconfig "{1}"' -f $PackageName, $tmpNpmrc
690+
$verifyCmd = 'npm list -g --depth=0 "{0}"' -f $PackageName
545691
Run-NpmViaCmd $verifyCmd
546692
}
547693

@@ -554,13 +700,19 @@ try {
554700
Write-Host " 🚀 " -NoNewline -ForegroundColor Magenta
555701
$startChoice = Read-Host "Start $PackageName now? [Y/n]"
556702
if ([string]::IsNullOrWhiteSpace($startChoice) -or $startChoice -match '^[Yy]') {
703+
Write-Host ""
704+
Write-Host " 🏢 " -NoNewline -ForegroundColor Cyan
705+
$adoOrg = Read-Host "Enter your ADO org name [microsoft]"
706+
if ([string]::IsNullOrWhiteSpace($adoOrg)) { $adoOrg = "microsoft" }
707+
$env:DEEPSTUDIO_ADO_ORG = $adoOrg
708+
Dim "ADO org: $adoOrg"
557709
Write-Host ""
558710
Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Magenta
559711
Success "Launching $PackageName (press Ctrl+C to stop)..."
560712
Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Magenta
561713
Write-Host ""
562714
if ($DryRun) {
563-
Dim "DRYRUN: would run $PackageName"
715+
Dim "DRYRUN: would run $PackageName (ADO org: $adoOrg)"
564716
} else {
565717
& $PackageName
566718
}
@@ -584,6 +736,15 @@ catch {
584736
Dim " • Wrong registry URL"
585737
Dim " • No permission to the Azure Artifacts feed"
586738
Dim " • Corporate proxy/SSL interception issues"
739+
Dim " • npm 11+: built-in npmrc at <npm-prefix>/node_modules/npm/npmrc has conflicting auth"
740+
Dim " (check: npm config get prefix then inspect <prefix>/node_modules/npm/npmrc)"
741+
742+
Write-Host ""
743+
Warn "Common causes for 404:"
744+
Dim " • A project-level .npmrc in the current directory set a different registry"
745+
Dim " • NPM_CONFIG_REGISTRY env var overrode the --registry flag"
746+
Dim " • The ADO feed is missing the npmjs.org upstream source"
747+
Dim " • Try running from a clean directory (e.g. cd $env:TEMP)"
587748

588749
if ($VerboseInstall) {
589750
Write-Host ""
@@ -599,11 +760,17 @@ catch {
599760
if ($EnableLog -and -not $DryRun) {
600761
Write-Host ""
601762
Warn "Log saved to: $LogPath"
763+
} else {
764+
Dump-LogBuffer
602765
}
603766

604767
throw
605768
}
606769
finally {
770+
# Restore env var
771+
if ($script:SavedNpmConfigRegistry) {
772+
$env:NPM_CONFIG_REGISTRY = $script:SavedNpmConfigRegistry
773+
}
607774
if (-not $DryRun) {
608775
Remove-Item $tmpNpmrc -Force -ErrorAction SilentlyContinue
609776
}

0 commit comments

Comments
 (0)