easy-devops 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -0
  3. package/cli/index.js +91 -0
  4. package/cli/managers/domain-manager.js +451 -0
  5. package/cli/managers/nginx-manager.js +329 -0
  6. package/cli/managers/node-manager.js +275 -0
  7. package/cli/managers/ssl-manager.js +397 -0
  8. package/cli/menus/.gitkeep +0 -0
  9. package/cli/menus/dashboard.js +223 -0
  10. package/cli/menus/domains.js +5 -0
  11. package/cli/menus/nginx.js +5 -0
  12. package/cli/menus/nodejs.js +5 -0
  13. package/cli/menus/settings.js +83 -0
  14. package/cli/menus/ssl.js +5 -0
  15. package/core/config.js +37 -0
  16. package/core/db.js +30 -0
  17. package/core/detector.js +257 -0
  18. package/core/nginx-conf-generator.js +309 -0
  19. package/core/shell.js +151 -0
  20. package/dashboard/lib/.gitkeep +0 -0
  21. package/dashboard/lib/cert-reader.js +59 -0
  22. package/dashboard/lib/domains-db.js +51 -0
  23. package/dashboard/lib/nginx-conf-generator.js +16 -0
  24. package/dashboard/lib/nginx-service.js +282 -0
  25. package/dashboard/public/js/app.js +486 -0
  26. package/dashboard/routes/.gitkeep +0 -0
  27. package/dashboard/routes/auth.js +30 -0
  28. package/dashboard/routes/domains.js +300 -0
  29. package/dashboard/routes/nginx.js +151 -0
  30. package/dashboard/routes/settings.js +78 -0
  31. package/dashboard/routes/ssl.js +105 -0
  32. package/dashboard/server.js +79 -0
  33. package/dashboard/views/index.ejs +327 -0
  34. package/dashboard/views/partials/domain-form.ejs +229 -0
  35. package/dashboard/views/partials/domains-panel.ejs +66 -0
  36. package/dashboard/views/partials/login.ejs +50 -0
  37. package/dashboard/views/partials/nginx-panel.ejs +90 -0
  38. package/dashboard/views/partials/overview.ejs +67 -0
  39. package/dashboard/views/partials/settings-panel.ejs +37 -0
  40. package/dashboard/views/partials/sidebar.ejs +45 -0
  41. package/dashboard/views/partials/ssl-panel.ejs +53 -0
  42. package/data/.gitkeep +0 -0
  43. package/install.bat +41 -0
  44. package/install.ps1 +653 -0
  45. package/install.sh +452 -0
  46. package/lib/installer/.gitkeep +0 -0
  47. package/lib/installer/detect.sh +88 -0
  48. package/lib/installer/node-versions.sh +109 -0
  49. package/lib/installer/nvm-bootstrap.sh +77 -0
  50. package/lib/installer/picker.sh +163 -0
  51. package/lib/installer/progress.sh +25 -0
  52. package/package.json +67 -0
package/install.ps1 ADDED
@@ -0,0 +1,653 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Easy DevOps Bootstrap Installer for Windows
4
+
5
+ .DESCRIPTION
6
+ Installs Node.js (via nvm-windows), project dependencies, and registers
7
+ the easy-devops CLI command globally.
8
+
9
+ .PARAMETER Help
10
+ Print this help message and exit.
11
+
12
+ .PARAMETER Version
13
+ Skip the version picker and use the specified Node.js major version.
14
+ Example: -Version 20
15
+
16
+ .PARAMETER KeepNode
17
+ Skip Node.js management entirely. Proceeds directly to dependency
18
+ installation using whatever Node.js is currently active.
19
+
20
+ .EXAMPLE
21
+ .\install.ps1 # Interactive install
22
+ .\install.ps1 -Version 20 # Install Node.js 20.x (latest patch via nvm)
23
+ .\install.ps1 -KeepNode # Skip Node.js management
24
+ #>
25
+ param(
26
+ [switch]$Help,
27
+ [string]$Version = "",
28
+ [switch]$KeepNode
29
+ )
30
+
31
+ Set-StrictMode -Version Latest
32
+ $ErrorActionPreference = 'Stop'
33
+
34
+ $REQUIRED_NODE_MAJOR = 18
35
+ $NVM_WINDOWS_VERSION = '1.1.12'
36
+ $NODE_FALLBACK = '20'
37
+
38
+ # ─── Summary tracking (mirrors install.sh add_result) ────────────────────────
39
+
40
+ $stepResults = [System.Collections.Generic.List[PSCustomObject]]::new()
41
+
42
+ function Add-Result {
43
+ param([string]$Name, [bool]$OK, [string]$Detail = '')
44
+ $script:stepResults.Add([PSCustomObject]@{ Name = $Name; OK = $OK; Detail = $Detail })
45
+ }
46
+
47
+ # ─── Output helpers ───────────────────────────────────────────────────────────
48
+
49
+ $script:currentStep = 0
50
+ $script:totalSteps = 7 # always 7; package-mode steps shown as skipped
51
+
52
+ function Write-Step {
53
+ param([string]$msg)
54
+ $script:currentStep++
55
+ Write-Host ""
56
+ Write-Host " [$script:currentStep/$script:totalSteps] $msg" -ForegroundColor Cyan
57
+ Write-Host " $('-' * 50)" -ForegroundColor DarkGray
58
+ }
59
+
60
+ function Write-OK { param([string]$msg) Write-Host " OK $msg" -ForegroundColor Green }
61
+ function Write-Warn { param([string]$msg) Write-Host " WARN $msg" -ForegroundColor Yellow }
62
+ function Write-Err { param([string]$msg) Write-Host " ERROR $msg" -ForegroundColor Red }
63
+ function Write-Info { param([string]$msg) Write-Host " $msg" -ForegroundColor Gray }
64
+
65
+ function Refresh-Path {
66
+ $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' +
67
+ [System.Environment]::GetEnvironmentVariable('Path', 'User')
68
+ }
69
+
70
+ function Get-NodeMajor {
71
+ param([string]$version)
72
+ try { return [int](($version -replace '^v','').Split('.')[0]) } catch { return 0 }
73
+ }
74
+
75
+ # ─── Help output ──────────────────────────────────────────────────────────────
76
+
77
+ function Print-Help {
78
+ Write-Host ""
79
+ Write-Host "Easy DevOps Bootstrap Installer" -ForegroundColor Cyan
80
+ Write-Host ""
81
+ Write-Host "Usage:"
82
+ Write-Host " .\install.ps1 [OPTIONS]"
83
+ Write-Host ""
84
+ Write-Host "Options:"
85
+ Write-Host " -Help Print this help and exit"
86
+ Write-Host " -Version <ver> Skip the version picker; install specified Node.js major"
87
+ Write-Host " Example: -Version 20"
88
+ Write-Host " -KeepNode Skip Node.js management; use current Node.js on PATH"
89
+ Write-Host ""
90
+ Write-Host "Exit codes:"
91
+ Write-Host " 0 Installation completed successfully"
92
+ Write-Host " 1 Unrecoverable error"
93
+ Write-Host " 2 User cancelled"
94
+ Write-Host ""
95
+ Write-Host "Examples:"
96
+ Write-Host " .\install.ps1 # Interactive install"
97
+ Write-Host " .\install.ps1 -Version 20 # Install Node.js 20.x"
98
+ Write-Host " .\install.ps1 -KeepNode # Skip Node.js management"
99
+ Write-Host ""
100
+ }
101
+
102
+ if ($Help) {
103
+ Print-Help
104
+ exit 0
105
+ }
106
+
107
+ # ─── BITS/WebClient download with progress ────────────────────────────────────
108
+
109
+ function Download-File {
110
+ param([string]$Url, [string]$Dest, [string]$Label)
111
+
112
+ $bitsOK = $false
113
+ try { Import-Module BitsTransfer -ErrorAction Stop; $bitsOK = $true } catch {}
114
+
115
+ if ($bitsOK) {
116
+ try {
117
+ Write-Info "Downloading $Label..."
118
+ Start-BitsTransfer `
119
+ -Source $Url `
120
+ -Destination $Dest `
121
+ -DisplayName "Easy DevOps Installer" `
122
+ -Description "Downloading $Label" `
123
+ -ErrorAction Stop
124
+ return
125
+ } catch {
126
+ Write-Info "BITS unavailable, falling back to WebClient..."
127
+ }
128
+ }
129
+
130
+ Write-Info "Downloading $Label..."
131
+ $wc = New-Object System.Net.WebClient
132
+ $wc.Headers.Add('User-Agent', 'EasyDevOps-Installer/1.0')
133
+ try { $wc.DownloadFile($Url, $Dest) }
134
+ finally { $wc.Dispose() }
135
+ }
136
+
137
+ # ─── Fetch Node.js LTS versions from nodejs.org ───────────────────────────────
138
+
139
+ $script:ltsVersions = $null
140
+
141
+ function Fetch-NodeVersions {
142
+ Write-Info "Fetching available Node.js LTS versions from nodejs.org..."
143
+
144
+ try {
145
+ $releases = Invoke-RestMethod -Uri "https://nodejs.org/dist/index.json" -TimeoutSec 20
146
+
147
+ $script:ltsVersions = $releases |
148
+ Where-Object { $_.lts -and $_.lts -ne $false } |
149
+ Group-Object { ($_.version -replace '^v(\d+)\..*', '$1') } |
150
+ ForEach-Object {
151
+ $_.Group | Sort-Object { [System.Version]($_.version -replace '^v','') } -Descending |
152
+ Select-Object -First 1
153
+ } |
154
+ Sort-Object { [int]($_.version -replace '^v(\d+)\..*','$1') } -Descending |
155
+ Select-Object -First 6
156
+
157
+ if (-not $script:ltsVersions -or @($script:ltsVersions).Count -eq 0) {
158
+ Write-Warn "No LTS versions found in response."
159
+ return $false
160
+ }
161
+ return $true
162
+ } catch {
163
+ Write-Warn "Could not fetch version list: $_"
164
+ return $false
165
+ }
166
+ }
167
+
168
+ # ─── Interactive version picker ───────────────────────────────────────────────
169
+
170
+ function Select-NodeVersion {
171
+ param([string]$CurrentVersion = "")
172
+
173
+ $list = @($script:ltsVersions)
174
+
175
+ Write-Host ""
176
+ Write-Host " Available Node.js LTS versions:" -ForegroundColor Cyan
177
+ Write-Host ""
178
+
179
+ for ($i = 0; $i -lt $list.Count; $i++) {
180
+ $v = $list[$i]
181
+ $ltsName = if ($v.lts -is [string] -and $v.lts) { " ($($v.lts))" } else { "" }
182
+ $current = if ($CurrentVersion -and ($v.version -replace '^v(\d+)\..*','$1') -eq ($CurrentVersion -replace '^v(\d+)\..*','$1')) { " [current]" } else { "" }
183
+ $def = if ($i -eq 0) { " <- default" } else { "" }
184
+ Write-Host " [$($i+1)] $($v.version)$ltsName$current$def" -ForegroundColor White
185
+ }
186
+
187
+ Write-Host ""
188
+
189
+ $majorVersion = $NODE_FALLBACK
190
+ while ($true) {
191
+ $raw = Read-Host " Choose a version [1-$($list.Count), press Enter for default]"
192
+ if ([string]::IsNullOrWhiteSpace($raw)) { $raw = "1" }
193
+
194
+ if ($raw -match '^\d+$') {
195
+ $idx = [int]$raw - 1
196
+ if ($idx -ge 0 -and $idx -lt $list.Count) {
197
+ $majorVersion = ($list[$idx].version -replace '^v(\d+)\..*', '$1')
198
+ Write-OK "Selected $($list[$idx].version)"
199
+ return $majorVersion
200
+ }
201
+ }
202
+ Write-Warn "Please enter a number between 1 and $($list.Count)"
203
+ }
204
+ }
205
+
206
+ # ─── Detect install mode ──────────────────────────────────────────────────────
207
+ # Source mode = running from a git-cloned directory -> need npm install + link
208
+ # Package mode = easy-devops already globally installed via npm
209
+
210
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
211
+ $isSourceMode = $true
212
+ $existingCmd = $null
213
+
214
+ try {
215
+ $existingCmd = (& where.exe easy-devops 2>$null)
216
+ if ($LASTEXITCODE -eq 0 -and $existingCmd) {
217
+ $isSourceMode = $false
218
+ }
219
+ } catch {}
220
+
221
+ # ─── Banner ───────────────────────────────────────────────────────────────────
222
+
223
+ Write-Host ""
224
+ Write-Host " ==========================================" -ForegroundColor Cyan
225
+ Write-Host " Easy DevOps -- Windows Installer" -ForegroundColor Cyan
226
+ Write-Host " ==========================================" -ForegroundColor Cyan
227
+
228
+ if (-not $isSourceMode) {
229
+ Write-Host ""
230
+ Write-Host " Mode: package (easy-devops already installed at $existingCmd)" -ForegroundColor DarkGray
231
+ Write-Host " Skipping npm install / npm link steps." -ForegroundColor DarkGray
232
+ } else {
233
+ Write-Host ""
234
+ Write-Host " Mode: source (installing from project directory)" -ForegroundColor DarkGray
235
+ }
236
+
237
+ # ─── Step 1: Detect system ───────────────────────────────────────────────────
238
+
239
+ Write-Step "Detecting system"
240
+
241
+ $osVer = [System.Environment]::OSVersion.Version
242
+ if ($osVer.Major -lt 10) {
243
+ Write-Err "Windows 10 or later is required (found $($osVer.Major).$($osVer.Minor))"
244
+ exit 1
245
+ }
246
+ Write-OK "Windows $($osVer.Major).$($osVer.Minor)"
247
+ Add-Result "System detection" $true "Windows $($osVer.Major).$($osVer.Minor)"
248
+
249
+ if ($PSVersionTable.PSVersion.Major -lt 5) {
250
+ Write-Err "PowerShell 5.1+ is required (found $($PSVersionTable.PSVersion))"
251
+ exit 1
252
+ }
253
+ Write-OK "PowerShell $($PSVersionTable.PSVersion)"
254
+
255
+ if (-not $KeepNode) {
256
+ try {
257
+ $null = Invoke-WebRequest -Uri "https://nodejs.org" -UseBasicParsing -TimeoutSec 10
258
+ Write-OK "Internet connectivity confirmed"
259
+ } catch {
260
+ Write-Err "No internet connection. Please check your network and try again."
261
+ exit 1
262
+ }
263
+ }
264
+
265
+ # Detect existing Node.js
266
+ $nodeOK = $false
267
+ $nodeVersion = $null
268
+
269
+ try {
270
+ $raw = (& node --version 2>$null).Trim()
271
+ if ($LASTEXITCODE -eq 0 -and $raw -match '^v') {
272
+ $nodeVersion = $raw
273
+ $major = Get-NodeMajor $nodeVersion
274
+ if ($major -ge $REQUIRED_NODE_MAJOR) {
275
+ Write-OK "Node.js $nodeVersion -- compatible"
276
+ $nodeOK = $true
277
+ } else {
278
+ Write-Warn "Node.js $nodeVersion found but v$REQUIRED_NODE_MAJOR+ is required"
279
+ }
280
+ } else {
281
+ Write-Info "Node.js not found on PATH"
282
+ }
283
+ } catch {
284
+ Write-Info "Node.js not found on PATH"
285
+ }
286
+
287
+ # ─── Step 2: Fetch Node.js release list ──────────────────────────────────────
288
+
289
+ $NODE_ACTION = "" # keep | upgrade | switch
290
+ $NODE_TARGET = "" # major version string
291
+
292
+ if ($KeepNode) {
293
+ Write-Step "Fetching Node.js release list"
294
+ Write-OK "Skipped (--KeepNode)"
295
+ Add-Result "Node.js release list" $true "Skipped (-KeepNode)"
296
+ $NODE_ACTION = "keep"
297
+ } elseif ($Version -ne "") {
298
+ Write-Step "Fetching Node.js release list"
299
+ Write-OK "Skipped (-Version $Version specified)"
300
+ Add-Result "Node.js release list" $true "Skipped (-Version $Version)"
301
+ $NODE_ACTION = "switch"
302
+ $NODE_TARGET = $Version
303
+ } else {
304
+ Write-Step "Fetching Node.js release list"
305
+ $fetchOK = Fetch-NodeVersions
306
+ if (-not $fetchOK) {
307
+ Write-Warn "Using fallback version $NODE_FALLBACK."
308
+ $script:ltsVersions = @([PSCustomObject]@{ version = "v$NODE_FALLBACK.0.0"; lts = "LTS" })
309
+ }
310
+ Add-Result "Node.js release list" $true "@($script:ltsVersions).Count LTS versions"
311
+ }
312
+
313
+ # ─── Step 3: Node.js version selection ───────────────────────────────────────
314
+
315
+ if ($NODE_ACTION -eq "keep") {
316
+ Write-Step "Node.js version selection"
317
+ Write-OK "Skipped (-KeepNode)"
318
+ Add-Result "Node.js selection" $true "Skipped (-KeepNode)"
319
+ } elseif ($Version -ne "") {
320
+ Write-Step "Node.js version selection"
321
+ Write-OK "Skipped (using -Version $Version)"
322
+ Add-Result "Node.js selection" $true "$Version"
323
+ } else {
324
+ Write-Step "Node.js version selection"
325
+
326
+ if ($nodeOK) {
327
+ # Node >= 18 already installed: 3-option menu (mirrors install.sh)
328
+ Write-Host ""
329
+ Write-Host " Node.js $nodeVersion is already installed." -ForegroundColor White
330
+ Write-Host " What would you like to do?" -ForegroundColor White
331
+ Write-Host ""
332
+ Write-Host " [1] Keep current version ($nodeVersion)" -ForegroundColor White
333
+ Write-Host " [2] Upgrade to latest LTS automatically" -ForegroundColor White
334
+ Write-Host " [3] Switch to a different version (picker)" -ForegroundColor White
335
+ Write-Host ""
336
+
337
+ while ($true) {
338
+ $raw = Read-Host " Enter 1, 2, or 3 (q to quit)"
339
+ switch ($raw.Trim()) {
340
+ "1" {
341
+ $NODE_ACTION = "keep"
342
+ $NODE_TARGET = ""
343
+ break
344
+ }
345
+ "2" {
346
+ $NODE_ACTION = "upgrade"
347
+ # Pick the newest LTS from the fetched list
348
+ $list = @($script:ltsVersions)
349
+ if ($list.Count -gt 0) {
350
+ $NODE_TARGET = ($list[0].version -replace '^v(\d+)\..*','$1')
351
+ } else {
352
+ $NODE_TARGET = $NODE_FALLBACK
353
+ }
354
+ Write-OK "Upgrading to latest LTS (major $NODE_TARGET)"
355
+ break
356
+ }
357
+ "3" {
358
+ $NODE_ACTION = "switch"
359
+ $NODE_TARGET = Select-NodeVersion -CurrentVersion $nodeVersion
360
+ break
361
+ }
362
+ { $_ -eq "q" -or $_ -eq "Q" } {
363
+ Write-Host ""
364
+ Write-Host " Installation cancelled by user." -ForegroundColor Yellow
365
+ exit 2
366
+ }
367
+ default {
368
+ Write-Warn "Invalid choice. Please enter 1, 2, or 3."
369
+ continue
370
+ }
371
+ }
372
+ break
373
+ }
374
+
375
+ if ($NODE_ACTION -eq "keep") {
376
+ Write-OK "Keeping Node.js $nodeVersion"
377
+ Add-Result "Node.js selection" $true "Keep $nodeVersion"
378
+ } else {
379
+ Add-Result "Node.js selection" $true "$NODE_ACTION -> $NODE_TARGET"
380
+ }
381
+ } else {
382
+ # Node not installed or below 18: go straight to picker
383
+ if ($script:ltsVersions) {
384
+ $NODE_TARGET = Select-NodeVersion
385
+ } else {
386
+ $NODE_TARGET = $NODE_FALLBACK
387
+ Write-OK "Using default Node.js $NODE_TARGET"
388
+ }
389
+ $NODE_ACTION = "switch"
390
+ Add-Result "Node.js selection" $true "$NODE_TARGET"
391
+ }
392
+ }
393
+
394
+ # ─── Step 4: Install nvm-windows ─────────────────────────────────────────────
395
+
396
+ $nvmReady = $false
397
+ $nvmVersion = $null
398
+
399
+ if ($NODE_ACTION -eq "keep") {
400
+ Write-Step "Installing nvm-windows"
401
+ Write-OK "Skipped (keeping current Node.js)"
402
+ Add-Result "nvm-windows" $true "Skipped (keep)"
403
+ $nvmReady = $true
404
+ } else {
405
+ Write-Step "Installing nvm-windows"
406
+
407
+ # Check if nvm-windows is already present
408
+ try {
409
+ $nvmVersion = (& nvm version 2>$null).Trim()
410
+ if ($LASTEXITCODE -eq 0 -and $nvmVersion) {
411
+ Write-OK "nvm-windows $nvmVersion already installed"
412
+ Add-Result "nvm-windows" $true $nvmVersion
413
+ $nvmReady = $true
414
+ }
415
+ } catch {}
416
+
417
+ if (-not $nvmReady) {
418
+ $skipNvm = $false
419
+
420
+ if ($nodeOK) {
421
+ Write-Host ""
422
+ Write-Host " nvm-windows is not installed." -ForegroundColor Yellow
423
+ Write-Host " Required for 'nvm install $NODE_TARGET'. Install now?" -ForegroundColor Gray
424
+ Write-Host ""
425
+ $answer = Read-Host " Install nvm-windows? [Y/n]"
426
+ if ($answer -match '^[Nn]') {
427
+ Write-Info "Skipping nvm-windows."
428
+ Add-Result "nvm-windows" $true "Skipped (optional)"
429
+ $skipNvm = $true
430
+ }
431
+ }
432
+
433
+ if (-not $skipNvm) {
434
+ $installer = "$env:TEMP\nvm-setup.exe"
435
+ $nvmUrl = "https://github.com/coreybutler/nvm-windows/releases/download/$NVM_WINDOWS_VERSION/nvm-setup.exe"
436
+
437
+ try {
438
+ Download-File -Url $nvmUrl -Dest $installer -Label "nvm-windows $NVM_WINDOWS_VERSION"
439
+ Write-OK "Downloaded"
440
+ } catch {
441
+ Write-Err "Download failed: $_"
442
+ Write-Info "Manual: https://github.com/coreybutler/nvm-windows/releases"
443
+ Add-Result "nvm-windows" $false "Download failed"
444
+ if (-not $nodeOK) { exit 1 }
445
+ }
446
+
447
+ if (Test-Path $installer) {
448
+ try {
449
+ Write-Info "Running installer silently..."
450
+ $proc = Start-Process -FilePath $installer -ArgumentList '/S' -Wait -PassThru
451
+ if ($proc.ExitCode -ne 0) {
452
+ Write-Err "Installer exited with code $($proc.ExitCode)"
453
+ Add-Result "nvm-windows" $false "Exit code $($proc.ExitCode)"
454
+ if (-not $nodeOK) { exit 1 }
455
+ } else {
456
+ Write-OK "nvm-windows $NVM_WINDOWS_VERSION installed"
457
+ Add-Result "nvm-windows" $true $NVM_WINDOWS_VERSION
458
+ $nvmReady = $true
459
+ }
460
+ } catch {
461
+ Write-Err "Installer failed: $_"
462
+ Add-Result "nvm-windows" $false "$_"
463
+ if (-not $nodeOK) { exit 1 }
464
+ } finally {
465
+ try { Remove-Item $installer -Force -ErrorAction SilentlyContinue } catch {}
466
+ }
467
+
468
+ Refresh-Path
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ # ─── Step 5: Install Node.js via nvm ─────────────────────────────────────────
475
+
476
+ if ($NODE_ACTION -eq "keep") {
477
+ Write-Step "Installing Node.js via nvm"
478
+ Write-OK "Skipped (keeping current Node.js)"
479
+ Add-Result "Node.js install" $true "Skipped (keep)"
480
+ } else {
481
+ Write-Step "Installing Node.js via nvm"
482
+
483
+ if ($nvmReady -and -not $nodeOK) {
484
+ # No compatible Node.js: install chosen version
485
+ Write-Info "Installing Node.js $NODE_TARGET via nvm..."
486
+ try {
487
+ & nvm install $NODE_TARGET 2>&1 | ForEach-Object { Write-Info " $_" }
488
+ & nvm use $NODE_TARGET 2>&1 | ForEach-Object { Write-Info " $_" }
489
+ Refresh-Path
490
+
491
+ $raw = (& node --version 2>$null).Trim()
492
+ if ($raw -match '^v') {
493
+ $nodeVersion = $raw
494
+ Write-OK "Node.js $nodeVersion installed and active"
495
+ Add-Result "Node.js install" $true $nodeVersion
496
+ $nodeOK = $true
497
+ } else {
498
+ Write-Warn "nvm install ran but node is not yet on PATH"
499
+ Write-Warn "Open a NEW terminal, then run: nvm use $NODE_TARGET"
500
+ Add-Result "Node.js install" $false "PATH refresh needed -- open a new terminal"
501
+ }
502
+ } catch {
503
+ Write-Warn "nvm node install error: $_"
504
+ Write-Warn "Open a new terminal and run: nvm install $NODE_TARGET"
505
+ Add-Result "Node.js install" $false "Manual step needed"
506
+ }
507
+ } elseif ($nvmReady -and $nodeOK -and ($NODE_ACTION -eq "upgrade" -or $NODE_ACTION -eq "switch")) {
508
+ # Upgrade or switch: install the selected version
509
+ Write-Info "Installing Node.js $NODE_TARGET via nvm..."
510
+ try {
511
+ & nvm install $NODE_TARGET 2>&1 | ForEach-Object { Write-Info " $_" }
512
+ & nvm use $NODE_TARGET 2>&1 | ForEach-Object { Write-Info " $_" }
513
+ Refresh-Path
514
+
515
+ $raw = (& node --version 2>$null).Trim()
516
+ if ($raw -match '^v') {
517
+ $nodeVersion = $raw
518
+ Write-OK "Node.js $nodeVersion active"
519
+ Add-Result "Node.js install" $true $nodeVersion
520
+ $nodeOK = $true
521
+ } else {
522
+ Write-Warn "node not yet on PATH -- open a new terminal and run: nvm use $NODE_TARGET"
523
+ Add-Result "Node.js install" $false "PATH refresh needed"
524
+ $nodeOK = $false
525
+ }
526
+ } catch {
527
+ Write-Warn "nvm error: $_ -- open a new terminal and run: nvm use $NODE_TARGET"
528
+ Add-Result "Node.js install" $false "Manual step needed"
529
+ $nodeOK = $false
530
+ }
531
+ } elseif (-not $nvmReady -and $nodeOK) {
532
+ # nvm not available but Node >= 18 already present: skip
533
+ Write-OK "nvm not available; using existing Node.js $nodeVersion"
534
+ Add-Result "Node.js install" $true "Using existing $nodeVersion"
535
+ } else {
536
+ Write-Warn "No Node.js installed and nvm not available."
537
+ Write-Info "Install Node.js manually from: https://nodejs.org"
538
+ Add-Result "Node.js install" $false "Manual install required"
539
+ }
540
+ }
541
+
542
+ # ─── Source-mode steps ────────────────────────────────────────────────────────
543
+
544
+ if (-not $isSourceMode) {
545
+ # Package mode: steps 6+7 skipped
546
+ Write-Step "Installing Easy DevOps dependencies"
547
+ Write-OK "Skipped (package mode -- easy-devops already installed)"
548
+ Add-Result "npm install" $true "Skipped (package mode)"
549
+
550
+ Write-Step "Registering global command"
551
+ Write-OK "Skipped (package mode)"
552
+ Add-Result "CLI registered" $true "Skipped (package mode)"
553
+ } else {
554
+
555
+ # ─── Step 6: npm install ─────────────────────────────────────────────────────
556
+
557
+ Write-Step "Installing Easy DevOps dependencies"
558
+
559
+ $packageJson = Join-Path $scriptDir "package.json"
560
+ if (-not (Test-Path $packageJson)) {
561
+ Write-Err "package.json not found at: $packageJson"
562
+ Write-Err "Run this installer from the Easy DevOps project root."
563
+ exit 1
564
+ }
565
+
566
+ if (-not $nodeOK) {
567
+ Write-Warn "Skipping -- Node.js is not ready. Re-run install.ps1 after setting up Node.js."
568
+ Add-Result "npm install" $false "Skipped -- Node.js not ready"
569
+ } else {
570
+ try {
571
+ Push-Location $scriptDir
572
+ Write-Info "Running npm install..."
573
+ & npm install
574
+ if ($LASTEXITCODE -ne 0) {
575
+ Write-Err "npm install failed (exit code $LASTEXITCODE)"
576
+ Add-Result "npm install" $false "Exit code $LASTEXITCODE"
577
+ exit 1
578
+ }
579
+ Write-OK "All dependencies installed"
580
+ Add-Result "npm install" $true ""
581
+ } catch {
582
+ Write-Err "npm install error: $_"
583
+ Add-Result "npm install" $false "$_"
584
+ exit 1
585
+ } finally {
586
+ Pop-Location
587
+ }
588
+ }
589
+
590
+ # ─── Step 7: npm link ────────────────────────────────────────────────────────
591
+
592
+ Write-Step "Registering global command"
593
+
594
+ if (-not $nodeOK) {
595
+ Write-Warn "Skipping -- Node.js is not ready"
596
+ Add-Result "CLI registered" $false "Skipped -- Node.js not ready"
597
+ } else {
598
+ try {
599
+ Push-Location $scriptDir
600
+ & npm link
601
+ if ($LASTEXITCODE -ne 0) {
602
+ Write-Warn "npm link failed -- CLI won't be globally available"
603
+ Write-Warn "You can still run: node cli/index.js"
604
+ Add-Result "CLI registered" $false "Exit code $LASTEXITCODE"
605
+ } else {
606
+ Write-OK "easy-devops command linked globally"
607
+ Add-Result "CLI registered" $true ""
608
+ }
609
+ } catch {
610
+ Write-Warn "npm link failed: $_ -- run: node cli/index.js"
611
+ Add-Result "CLI registered" $false "$_"
612
+ } finally {
613
+ Pop-Location
614
+ }
615
+ }
616
+
617
+ } # end source-mode block
618
+
619
+ # ─── Summary (mirrors install.sh summary block) ───────────────────────────────
620
+
621
+ Write-Host ""
622
+ Write-Host " ==========================================" -ForegroundColor Cyan
623
+ Write-Host " Installation Summary" -ForegroundColor Cyan
624
+ Write-Host " ==========================================" -ForegroundColor Cyan
625
+ Write-Host ""
626
+
627
+ $allOK = $true
628
+ foreach ($r in $stepResults) {
629
+ if ($r.OK) {
630
+ $icon = " OK "
631
+ $color = 'Green'
632
+ } else {
633
+ $icon = "FAIL"
634
+ $color = 'Yellow'
635
+ $allOK = $false
636
+ }
637
+ $detail = if ($r.Detail) { " ($($r.Detail))" } else { "" }
638
+ Write-Host " [$icon] $($r.Name)$detail" -ForegroundColor $color
639
+ }
640
+
641
+ Write-Host ""
642
+ if ($allOK) {
643
+ Write-Host " All steps completed successfully!" -ForegroundColor Green
644
+ Write-Host ""
645
+ Write-Host " Run the CLI:" -ForegroundColor White
646
+ Write-Host " easy-devops" -ForegroundColor Cyan
647
+ } else {
648
+ Write-Host " Some steps need attention -- see warnings above." -ForegroundColor Yellow
649
+ Write-Host ""
650
+ Write-Host " Fallback:" -ForegroundColor White
651
+ Write-Host " node cli/index.js" -ForegroundColor Cyan
652
+ }
653
+ Write-Host ""