Skip to content

Commit 6905f48

Browse files
authored
Update private-install.ps1
1 parent 9853a4c commit 6905f48

1 file changed

Lines changed: 226 additions & 19 deletions

File tree

deepstudio/private-install.ps1

Lines changed: 226 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,38 @@
1+
param(
2+
[switch]$VerboseInstall
3+
)
4+
15
$ErrorActionPreference = "Stop"
26

7+
# ---------------------------
8+
# Settings / Feature flags
9+
# ---------------------------
310
$PackageName = if ($env:DEEPSTUDIO_PKG) { $env:DEEPSTUDIO_PKG } else { "deepstudio-server" }
411

12+
# Support both: param -VerboseInstall and env DEEPSTUDIO_VERBOSE=1
13+
$VerboseInstall = $VerboseInstall -or ($env:DEEPSTUDIO_VERBOSE -eq "1")
14+
15+
# Dry run: only print commands, do not modify npm config or install
16+
$DryRun = ($env:DEEPSTUDIO_DRY_RUN -eq "1")
17+
18+
# Save logs to file when DEEPSTUDIO_LOG=1
19+
$EnableLog = ($env:DEEPSTUDIO_LOG -eq "1")
20+
21+
# Registry can be provided by env var (best for CI / automation)
22+
$RegistryFromEnv = $env:DEEPSTUDIO_REGISTRY
23+
24+
# Optional override for log path
25+
$LogPath = if ($env:DEEPSTUDIO_LOG_PATH) { $env:DEEPSTUDIO_LOG_PATH } else {
26+
Join-Path (Get-Location) ("deepstudio-install-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".log")
27+
}
28+
29+
# ---------------------------
30+
# Helpers
31+
# ---------------------------
32+
function Info([string]$msg) { Write-Host $msg }
33+
function Warn([string]$msg) { Write-Host $msg }
34+
function Fail([string]$msg) { Write-Host $msg }
35+
536
function Require-Npm {
637
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
738
Write-Host ""
@@ -20,47 +51,223 @@ function Read-Secure([string]$msg) {
2051
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
2152
}
2253

23-
function SetCfg($k, $v) { npm config set --global $k $v | Out-Null }
24-
function DelCfg($k) { try { npm config delete --global $k | Out-Null } catch {} }
54+
function Mask-Token([string]$token) {
55+
if ([string]::IsNullOrEmpty($token)) { return "<empty>" }
56+
if ($token.Length -le 6) { return ("*" * $token.Length) }
57+
$head = $token.Substring(0, 3)
58+
$tail = $token.Substring($token.Length - 3, 3)
59+
return "$head" + ("*" * ($token.Length - 6)) + "$tail"
60+
}
2561

26-
Require-Npm
62+
function Ensure-Registry([string]$reg) {
63+
if ([string]::IsNullOrWhiteSpace($reg)) { throw "Registry URL is required." }
64+
$reg = $reg.Trim().TrimEnd("/")
65+
try { [void]([Uri]$reg) } catch { throw "Invalid registry URL: $reg" }
66+
return $reg
67+
}
68+
69+
function SetCfg([string]$k, [string]$v) {
70+
if ($DryRun) { Info "DRYRUN: npm config set --global $k <value>"; return }
71+
npm config set --global $k $v | Out-Null
72+
if ($LASTEXITCODE -ne 0) { throw "npm config set failed ($k). exit=$LASTEXITCODE" }
73+
}
74+
75+
function DelCfg([string]$k) {
76+
if ($DryRun) { Info "DRYRUN: npm config delete --global $k"; return }
77+
try {
78+
npm config delete --global $k | Out-Null
79+
} catch {}
80+
}
81+
82+
function Run-NpmInstall([string[]]$args) {
83+
if ($DryRun) {
84+
Info ("DRYRUN: npm " + ($args -join " "))
85+
return
86+
}
87+
88+
if (-not $EnableLog) {
89+
& npm @args
90+
if ($LASTEXITCODE -ne 0) {
91+
throw "npm failed with exit code $LASTEXITCODE. (Tip: set DEEPSTUDIO_LOG=1 to capture full logs.)"
92+
}
93+
return
94+
}
95+
96+
# Logging enabled: capture stdout/stderr to file AND show on console
97+
Info "Logging enabled. Log file: $LogPath"
98+
99+
$psi = New-Object System.Diagnostics.ProcessStartInfo
100+
$psi.FileName = "npm"
101+
$psi.Arguments = ($args -join " ")
102+
$psi.RedirectStandardOutput = $true
103+
$psi.RedirectStandardError = $true
104+
$psi.UseShellExecute = $false
105+
$psi.CreateNoWindow = $true
106+
107+
$p = New-Object System.Diagnostics.Process
108+
$p.StartInfo = $psi
109+
[void]$p.Start()
110+
111+
$outTask = $p.StandardOutput.ReadToEndAsync()
112+
$errTask = $p.StandardError.ReadToEndAsync()
113+
114+
$p.WaitForExit()
115+
116+
$stdout = $outTask.Result
117+
$stderr = $errTask.Result
118+
119+
if ($stdout) { Write-Host $stdout }
120+
if ($stderr) { Write-Host $stderr }
121+
122+
$content = @(
123+
"=== DeepStudio install log ==="
124+
"Time: $(Get-Date -Format o)"
125+
"Package: $PackageName@latest"
126+
"VerboseInstall: $VerboseInstall"
127+
"Registry: $registry/"
128+
"Command: npm " + ($args -join " ")
129+
""
130+
"---- STDOUT ----"
131+
$stdout
132+
""
133+
"---- STDERR ----"
134+
$stderr
135+
""
136+
"ExitCode: $($p.ExitCode)"
137+
) -join "`r`n"
27138

28-
$registry = Read-Host "Enter npm registry URL (for ex: https://xxx.pkgs.xxx.com/xxx/_packaging/xxx/npm/registry/)"
29-
if ([string]::IsNullOrWhiteSpace($registry)) {
30-
throw "Registry URL is required."
139+
Set-Content -Path $LogPath -Value $content -Encoding UTF8
140+
141+
if ($p.ExitCode -ne 0) {
142+
throw "npm failed with exit code $($p.ExitCode). See log: $LogPath"
143+
}
31144
}
32145

33-
# normalize registry
34-
$registry = $registry.TrimEnd("/")
146+
# ---------------------------
147+
# Start
148+
# ---------------------------
149+
Require-Npm
150+
151+
Info ""
152+
Info "=== DeepStudio npm installer ==="
153+
Info "Package: $PackageName@latest"
154+
Info ("VerboseInstall: " + $(if ($VerboseInstall) { "ON" } else { "OFF" }))
155+
Info ("DryRun: " + $(if ($DryRun) { "ON" } else { "OFF" }))
156+
Info ("LogToFile: " + $(if ($EnableLog) { "ON ($LogPath)" } else { "OFF" }))
157+
Info ""
35158

36-
# derive auth prefix
159+
# Get registry: env > prompt
160+
$registryInput = $RegistryFromEnv
161+
if ([string]::IsNullOrWhiteSpace($registryInput)) {
162+
$registryInput = Read-Host "Enter npm registry URL (ex: https://xxx.pkgs.xxx.com/xxx/_packaging/xxx/npm/registry/)"
163+
}
164+
$registry = Ensure-Registry $registryInput
165+
166+
# derive auth prefix (npmrc style)
37167
$uri = [Uri]$registry
38168
$authPrefix = "//" + $uri.Host + $uri.AbsolutePath + "/"
39169

40-
$pat = Read-Secure "Enter Azure DevOps PAT (Packaging:Read)"
41-
if ([string]::IsNullOrWhiteSpace($pat)) {
42-
throw "PAT is empty."
43-
}
170+
# Read PAT securely then TRIM it (fix common 401 due to whitespace/newlines)
171+
$patRaw = Read-Secure "Enter Azure DevOps PAT (Packaging:Read)"
172+
if ([string]::IsNullOrWhiteSpace($patRaw)) { throw "PAT is empty." }
173+
174+
$pat = $patRaw.Trim()
175+
$patRaw = $null
176+
177+
Info ""
178+
Info ("PAT (masked): " + (Mask-Token $pat))
179+
Info "Tip: if masked prefix/suffix looks wrong, you likely pasted extra chars/spaces."
180+
Info ""
44181

45182
$patB64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($pat))
46183
$pat = $null
47184

48-
try {
49-
Write-Host "Installing $PackageName@latest"
185+
# npm install args
186+
$logLevel = if ($VerboseInstall) { "verbose" } else { "notice" }
187+
$npmInstallArgs = @(
188+
"install", "-g", "$PackageName@latest",
189+
"--registry", "$registry/",
190+
"--loglevel", $logLevel
191+
)
50192

193+
# Optional: extra debug signal for npm
194+
if ($VerboseInstall) {
195+
$env:NPM_CONFIG_LOGLEVEL = "verbose"
196+
$env:NPM_CONFIG_PROGRESS = "false"
197+
}
198+
199+
try {
200+
Info "Configuring npm auth for this registry..."
51201
SetCfg "registry" "$registry/"
52202
SetCfg "${authPrefix}:username" "ms"
53203
SetCfg "${authPrefix}:_password" $patB64
54204
SetCfg "${authPrefix}:email" "npm@example.com"
205+
SetCfg "always-auth" "true"
206+
207+
Info ""
208+
Info ("Command: npm " + ($npmInstallArgs -join " "))
209+
Info ""
55210

56-
npm install -g "$PackageName@latest" --registry "$registry/"
57-
Write-Host "✅ Installed $PackageName@latest successfully."
58-
Write-Host "You can now run $PackageName to launch it."
211+
Run-NpmInstall $npmInstallArgs
212+
213+
# Extra safety: verify it's actually installed
214+
if (-not $DryRun) {
215+
npm list -g --depth=0 "$PackageName" | Out-Null
216+
if ($LASTEXITCODE -ne 0) {
217+
throw "Install command finished but package not found in global npm list. Something is wrong."
218+
}
219+
}
220+
221+
Info ""
222+
Info "✅ Installed $PackageName@latest successfully."
223+
Info "You can now run: $PackageName"
224+
}
225+
catch {
226+
Info ""
227+
Fail "❌ Installation failed."
228+
Fail ("Error: " + $_.Exception.Message)
229+
230+
# Helpful hints for common 401/403 issues
231+
Warn ""
232+
Warn "Common causes for 401/403:"
233+
Warn " - PAT missing Packaging:Read scope"
234+
Warn " - PAT pasted with extra whitespace (we trimmed, but double-check the masked value)"
235+
Warn " - Wrong registry URL (must be the npm/registry endpoint)"
236+
Warn " - No permission to the Azure Artifacts feed"
237+
Warn " - Corporate proxy/SSL interception issues"
238+
239+
if ($VerboseInstall) {
240+
Info ""
241+
Info "---- Full exception ----"
242+
Info $_.Exception.ToString()
243+
Info "------------------------"
244+
} else {
245+
Warn ""
246+
Warn "Tip: re-run with verbose:"
247+
Warn " `$env:DEEPSTUDIO_VERBOSE='1'; irm <url> | iex"
248+
}
249+
250+
if ($EnableLog -and -not $DryRun) {
251+
Warn ""
252+
Warn "Log saved to: $LogPath"
253+
}
254+
255+
throw
59256
}
60257
finally {
258+
Info ""
259+
Info "Cleaning up npm global config..."
61260
DelCfg "registry"
62261
DelCfg "${authPrefix}:username"
63262
DelCfg "${authPrefix}:_password"
64263
DelCfg "${authPrefix}:email"
65-
Write-Host "✅ Cleanup complete."
264+
DelCfg "always-auth"
265+
266+
if ($VerboseInstall) {
267+
Remove-Item Env:\NPM_CONFIG_LOGLEVEL -ErrorAction SilentlyContinue
268+
Remove-Item Env:\NPM_CONFIG_PROGRESS -ErrorAction SilentlyContinue
269+
}
270+
271+
Info "✅ Cleanup complete."
272+
Info ""
66273
}

0 commit comments

Comments
 (0)