vanspod 0.1.2 → 0.1.3

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.
@@ -1,133 +1,133 @@
1
- param([string]$Plan = (Join-Path $PSScriptRoot 'uninstall-plan.json'))
2
- $ErrorActionPreference = 'Stop'
3
- $utf8 = New-Object System.Text.UTF8Encoding($false)
4
- $planSource = [System.IO.File]::ReadAllText($Plan, $utf8)
5
- $toolSource = [System.IO.File]::ReadAllText($PSCommandPath, $utf8)
6
- $planData = $planSource | ConvertFrom-Json
7
- $localRoot = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } else { $env:USERPROFILE }
8
- $expectedRoot = [System.IO.Path]::GetFullPath((Join-Path $localRoot 'VansPod'))
9
- $root = [System.IO.Path]::GetFullPath($planData.root)
10
- $dataRoot = [System.IO.Path]::GetFullPath($planData.dataRoot)
11
- if ($planData.version -ne 1 -or $root -ne $expectedRoot -or $dataRoot -ne [System.IO.Path]::GetFullPath((Join-Path $env:USERPROFILE '.vanspod')) -or [System.IO.Path]::GetFullPath($Plan) -ne (Join-Path $root 'uninstall-plan.json')) { throw 'Invalid removal plan' }
12
- [System.IO.File]::WriteAllText((Join-Path $root 'uninstall-ready'), (@{pid = $PID} | ConvertTo-Json -Compress), $utf8)
13
-
14
- function Assert-NoLinks([string]$Target, [bool]$InspectChildren = $true) {
15
- # A regular leaf can still resolve through a junction in any ancestor.
16
- $ancestor = [System.IO.Path]::GetFullPath($Target)
17
- while ($ancestor) {
18
- if ((Test-Path -LiteralPath $ancestor) -and ((Get-Item -LiteralPath $ancestor -Force).Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { throw "拒絕移除連結:$ancestor" }
19
- $ancestor = [System.IO.Path]::GetDirectoryName($ancestor)
20
- }
21
- if (Test-Path -LiteralPath $Target) {
22
- $item = Get-Item -LiteralPath $Target -Force
23
- if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw "拒絕移除連結:$Target" }
24
- if ($InspectChildren -and $item.PSIsContainer) { foreach ($child in Get-ChildItem -LiteralPath $Target -Force) { Assert-NoLinks $child.FullName } }
25
- }
26
- }
27
-
28
- function Remove-Owned([string]$Name, [string]$Directory = $root) {
29
- $target = [System.IO.Path]::GetFullPath((Join-Path $Directory $Name))
30
- if (-not $target.StartsWith($Directory + [System.IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { throw 'Removal target escaped VansPod data directory' }
31
- Assert-NoLinks $target
32
- if (Test-Path -LiteralPath $target) { Remove-Item -LiteralPath $target -Recurse -Force }
33
- }
34
-
35
- $failedTarget = $root
36
- try {
37
- # Validate roots and ancestors now, but the worker still owns mutable runtime
38
- # entries until it exits. Inspect their final tree only after that handoff.
39
- Assert-NoLinks $root $false
40
- Assert-NoLinks $dataRoot $false
41
- foreach ($processId in @($planData.pid, $planData.waitForPid)) {
42
- if ($processId) {
43
- for ($attempt = 0; $attempt -lt 300 -and (Get-Process -Id $processId -ErrorAction SilentlyContinue); $attempt++) { Start-Sleep -Milliseconds 100 }
44
- if (Get-Process -Id $processId -ErrorAction SilentlyContinue) { throw 'VansPod 尚未退出,請結束後重試。' }
45
- }
46
- }
47
- Assert-NoLinks $root
48
- Assert-NoLinks $dataRoot
49
- $currentStateFile = Join-Path $root 'runtime/instance.json'
50
- if (Test-Path -LiteralPath (Join-Path $root 'runtime')) {
51
- if (-not (Test-Path -LiteralPath $currentStateFile)) { throw 'VansPod 正在啟動,請從畫面或 vanspod uninstall 重試完整移除。' }
52
- $currentState = [System.IO.File]::ReadAllText($currentStateFile, $utf8) | ConvertFrom-Json
53
- if ($currentState.id -ne $planData.instanceId) { throw 'VansPod 已有新的執行紀錄,請從畫面或 vanspod uninstall 重試完整移除,以先恢復客戶端設定。' }
54
- if (Get-Process -Id $currentState.pid -ErrorAction SilentlyContinue) { throw 'VansPod 已重新啟動,請從畫面或 vanspod uninstall 重試完整移除。' }
55
- }
56
- foreach ($name in @('connection.json', 'settings.json', 'vscode-route-journal.json')) { $failedTarget = Join-Path $dataRoot $name; Remove-Owned $name $dataRoot }
57
- $legacyRoot = [System.IO.Path]::GetFullPath((Join-Path $localRoot 'classroom-app'))
58
- $failedTarget = Join-Path $legacyRoot 'connection.json'
59
- if (Test-Path -LiteralPath $legacyRoot) {
60
- if ((Get-Item -LiteralPath $legacyRoot -Force).Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw '拒絕清理舊憑證資料夾連結。' }
61
- Remove-Owned 'connection.json' $legacyRoot
62
- if (-not @(Get-ChildItem -LiteralPath $legacyRoot -Force).Count) { Remove-Item -LiteralPath $legacyRoot -Force }
63
- }
64
- if (Test-Path -LiteralPath $dataRoot) {
65
- foreach ($file in Get-ChildItem -LiteralPath $dataRoot -File -Force) {
66
- if ($file.Name -match '^(connection\.json|settings\.json|vscode-route-journal\.json)\..*tmp$') { $failedTarget = $file.FullName; Remove-Owned $file.Name $dataRoot }
67
- }
68
- if (-not @(Get-ChildItem -LiteralPath $dataRoot -Force).Count) { Remove-Item -LiteralPath $dataRoot -Force }
69
- }
70
- foreach ($name in @('runtime', 'desktop-assets', 'logs', 'installations.lock', 'installations.json.tmp', 'removal-result.json', 'startup-error.json')) {
71
- $failedTarget = Join-Path $root $name
72
- Remove-Owned $name
73
- }
74
- foreach ($installation in $planData.installations) {
75
- if ($installation.kind -eq 'exe') {
76
- $failedTarget = $installation.file
77
- $target = [System.IO.Path]::GetFullPath($installation.file)
78
- if ([System.IO.Path]::GetExtension($target) -ne '.exe' -or $installation.sha256 -notmatch '^[a-f0-9]{64}$') { throw 'Invalid executable registration' }
79
- Assert-NoLinks $target
80
- if (Test-Path -LiteralPath $target) {
81
- $sha = [System.Security.Cryptography.SHA256]::Create()
82
- try { $actualHash = [BitConverter]::ToString($sha.ComputeHash([System.IO.File]::ReadAllBytes($target))).Replace('-', '').ToLowerInvariant() }
83
- finally { $sha.Dispose() }
84
- if ($actualHash -ne $installation.sha256) { throw '檔案內容已改變,請重新開啟該 VansPod 後重試移除。' }
85
- Remove-Item -LiteralPath $target -Force
86
- }
87
- } elseif ($installation.kind -eq 'npm') {
88
- $failedTarget = $installation.root
89
- $prefix = [System.IO.Path]::GetFullPath($installation.prefix)
90
- $packageRoot = [System.IO.Path]::GetFullPath($installation.root)
91
- if ($packageRoot -ne (Join-Path $prefix 'node_modules/vanspod')) { throw 'Invalid npm registration' }
92
- Assert-NoLinks $packageRoot
93
- if (Test-Path -LiteralPath $packageRoot) {
94
- $package = [System.IO.File]::ReadAllText((Join-Path $packageRoot 'package.json'), $utf8) | ConvertFrom-Json
95
- if ($package.name -ne 'vanspod' -or $package.bin.vanspod -ne 'bin/vanspod.cjs') { throw 'Registered package is not VansPod' }
96
- & $installation.node $installation.npmCli uninstall --global --prefix $prefix vanspod --ignore-scripts 2>&1 | Out-Null
97
- if ($LASTEXITCODE -ne 0 -or (Test-Path -LiteralPath $packageRoot)) { throw 'npm 移除失敗。' }
98
- }
99
- } else { throw 'Unknown installation kind' }
100
- }
101
- foreach ($name in @('installations.json', 'uninstall-plan.json', 'uninstall-ready')) { $failedTarget = Join-Path $root $name; Remove-Owned $name }
102
- $failedTarget = $PSCommandPath
103
- Remove-Owned 'uninstall-tool.ps1'
104
- if (-not @(Get-ChildItem -LiteralPath $root -Force).Count) { Remove-Item -LiteralPath $root -Force }
105
- if (-not $planData.headless) { Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('VansPod 已完整移除。學生專案與獨立工具已保留;未登記的其他 exe 副本請自行刪除。', 'VansPod') | Out-Null }
106
- } catch {
107
- # A late cleanup error may have already removed a control file. Re-create
108
- # the exact plan and tool so partial removal always remains retryable.
109
- if (-not (Test-Path -LiteralPath $root)) { New-Item -ItemType Directory -Path $root | Out-Null }
110
- Assert-NoLinks $root
111
- [System.IO.File]::WriteAllText((Join-Path $root 'uninstall-plan.json'), $planSource, $utf8)
112
- [System.IO.File]::WriteAllText((Join-Path $root 'uninstall-tool.ps1'), $toolSource, (New-Object System.Text.UTF8Encoding($true)))
113
- $reason = $_.Exception.Message
114
- $remaining = @($failedTarget)
115
- foreach ($installation in $planData.installations) {
116
- $location = if ($installation.kind -eq 'exe') { $installation.file } else { $installation.root }
117
- if (Test-Path -LiteralPath $location) { $remaining += $location }
118
- }
119
- foreach ($name in @('runtime', 'desktop-assets', 'logs', 'installations.json', 'installations.lock', 'installations.json.tmp', 'uninstall-plan.json', 'uninstall-tool.ps1', 'uninstall-ready', 'startup-error.json', 'removal-result.json')) {
120
- $location = Join-Path $root $name
121
- if (Test-Path -LiteralPath $location) { $remaining += $location }
122
- }
123
- $legacyFile = Join-Path $localRoot 'classroom-app/connection.json'
124
- if (Test-Path -LiteralPath $legacyFile) { $remaining += $legacyFile }
125
- if (Test-Path -LiteralPath $dataRoot) {
126
- foreach ($file in Get-ChildItem -LiteralPath $dataRoot -File -Force) { if ($file.Name -match '^(connection\.json|settings\.json|vscode-route-journal\.json)(\..*tmp)?$') { $remaining += $file.FullName } }
127
- }
128
- $remaining = @($remaining | Select-Object -Unique)
129
- $message = "移除未完成:$reason`n剩餘項目:`n$($remaining -join "`n")`n請重試完整移除;若 VansPod 已重新啟動,請從畫面或 vanspod uninstall 重試。保留的 uninstall-tool.ps1 僅在服務已結束時可直接執行。"
130
- [System.IO.File]::WriteAllText((Join-Path $root 'removal-result.json'), (@{ok = $false; message = $message; remaining = $remaining} | ConvertTo-Json), $utf8)
131
- if (-not $planData.headless) { Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show($message, 'VansPod') | Out-Null }
132
- exit 1
133
- }
1
+ param([string]$Plan = (Join-Path $PSScriptRoot 'uninstall-plan.json'))
2
+ $ErrorActionPreference = 'Stop'
3
+ $utf8 = New-Object System.Text.UTF8Encoding($false)
4
+ $planSource = [System.IO.File]::ReadAllText($Plan, $utf8)
5
+ $toolSource = [System.IO.File]::ReadAllText($PSCommandPath, $utf8)
6
+ $planData = $planSource | ConvertFrom-Json
7
+ $localRoot = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } else { $env:USERPROFILE }
8
+ $expectedRoot = [System.IO.Path]::GetFullPath((Join-Path $localRoot 'VansPod'))
9
+ $root = [System.IO.Path]::GetFullPath($planData.root)
10
+ $dataRoot = [System.IO.Path]::GetFullPath($planData.dataRoot)
11
+ if ($planData.version -ne 1 -or $root -ne $expectedRoot -or $dataRoot -ne [System.IO.Path]::GetFullPath((Join-Path $env:USERPROFILE '.vanspod')) -or [System.IO.Path]::GetFullPath($Plan) -ne (Join-Path $root 'uninstall-plan.json')) { throw 'Invalid removal plan' }
12
+ [System.IO.File]::WriteAllText((Join-Path $root 'uninstall-ready'), (@{pid = $PID} | ConvertTo-Json -Compress), $utf8)
13
+
14
+ function Assert-NoLinks([string]$Target, [bool]$InspectChildren = $true) {
15
+ # A regular leaf can still resolve through a junction in any ancestor.
16
+ $ancestor = [System.IO.Path]::GetFullPath($Target)
17
+ while ($ancestor) {
18
+ if ((Test-Path -LiteralPath $ancestor) -and ((Get-Item -LiteralPath $ancestor -Force).Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { throw "拒絕移除連結:$ancestor" }
19
+ $ancestor = [System.IO.Path]::GetDirectoryName($ancestor)
20
+ }
21
+ if (Test-Path -LiteralPath $Target) {
22
+ $item = Get-Item -LiteralPath $Target -Force
23
+ if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw "拒絕移除連結:$Target" }
24
+ if ($InspectChildren -and $item.PSIsContainer) { foreach ($child in Get-ChildItem -LiteralPath $Target -Force) { Assert-NoLinks $child.FullName } }
25
+ }
26
+ }
27
+
28
+ function Remove-Owned([string]$Name, [string]$Directory = $root) {
29
+ $target = [System.IO.Path]::GetFullPath((Join-Path $Directory $Name))
30
+ if (-not $target.StartsWith($Directory + [System.IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { throw 'Removal target escaped VansPod data directory' }
31
+ Assert-NoLinks $target
32
+ if (Test-Path -LiteralPath $target) { Remove-Item -LiteralPath $target -Recurse -Force }
33
+ }
34
+
35
+ $failedTarget = $root
36
+ try {
37
+ # Validate roots and ancestors now, but the worker still owns mutable runtime
38
+ # entries until it exits. Inspect their final tree only after that handoff.
39
+ Assert-NoLinks $root $false
40
+ Assert-NoLinks $dataRoot $false
41
+ foreach ($processId in @($planData.pid, $planData.waitForPid)) {
42
+ if ($processId) {
43
+ for ($attempt = 0; $attempt -lt 300 -and (Get-Process -Id $processId -ErrorAction SilentlyContinue); $attempt++) { Start-Sleep -Milliseconds 100 }
44
+ if (Get-Process -Id $processId -ErrorAction SilentlyContinue) { throw 'VansPod 尚未退出,請結束後重試。' }
45
+ }
46
+ }
47
+ Assert-NoLinks $root
48
+ Assert-NoLinks $dataRoot
49
+ $currentStateFile = Join-Path $root 'runtime/instance.json'
50
+ if (Test-Path -LiteralPath (Join-Path $root 'runtime')) {
51
+ if (-not (Test-Path -LiteralPath $currentStateFile)) { throw 'VansPod 正在啟動,請從畫面或 vanspod uninstall 重試完整移除。' }
52
+ $currentState = [System.IO.File]::ReadAllText($currentStateFile, $utf8) | ConvertFrom-Json
53
+ if ($currentState.id -ne $planData.instanceId) { throw 'VansPod 已有新的執行紀錄,請從畫面或 vanspod uninstall 重試完整移除,以先恢復客戶端設定。' }
54
+ if (Get-Process -Id $currentState.pid -ErrorAction SilentlyContinue) { throw 'VansPod 已重新啟動,請從畫面或 vanspod uninstall 重試完整移除。' }
55
+ }
56
+ foreach ($name in @('connection.json', 'settings.json', 'vscode-route-journal.json')) { $failedTarget = Join-Path $dataRoot $name; Remove-Owned $name $dataRoot }
57
+ $legacyRoot = [System.IO.Path]::GetFullPath((Join-Path $localRoot 'classroom-app'))
58
+ $failedTarget = Join-Path $legacyRoot 'connection.json'
59
+ if (Test-Path -LiteralPath $legacyRoot) {
60
+ if ((Get-Item -LiteralPath $legacyRoot -Force).Attributes -band [System.IO.FileAttributes]::ReparsePoint) { throw '拒絕清理舊憑證資料夾連結。' }
61
+ Remove-Owned 'connection.json' $legacyRoot
62
+ if (-not @(Get-ChildItem -LiteralPath $legacyRoot -Force).Count) { Remove-Item -LiteralPath $legacyRoot -Force }
63
+ }
64
+ if (Test-Path -LiteralPath $dataRoot) {
65
+ foreach ($file in Get-ChildItem -LiteralPath $dataRoot -File -Force) {
66
+ if ($file.Name -match '^(connection\.json|settings\.json|vscode-route-journal\.json)\..*tmp$') { $failedTarget = $file.FullName; Remove-Owned $file.Name $dataRoot }
67
+ }
68
+ if (-not @(Get-ChildItem -LiteralPath $dataRoot -Force).Count) { Remove-Item -LiteralPath $dataRoot -Force }
69
+ }
70
+ foreach ($name in @('runtime', 'desktop-assets', 'logs', 'installations.lock', 'installations.json.tmp', 'removal-result.json', 'startup-error.json')) {
71
+ $failedTarget = Join-Path $root $name
72
+ Remove-Owned $name
73
+ }
74
+ foreach ($installation in $planData.installations) {
75
+ if ($installation.kind -eq 'exe') {
76
+ $failedTarget = $installation.file
77
+ $target = [System.IO.Path]::GetFullPath($installation.file)
78
+ if ([System.IO.Path]::GetExtension($target) -ne '.exe' -or $installation.sha256 -notmatch '^[a-f0-9]{64}$') { throw 'Invalid executable registration' }
79
+ Assert-NoLinks $target
80
+ if (Test-Path -LiteralPath $target) {
81
+ $sha = [System.Security.Cryptography.SHA256]::Create()
82
+ try { $actualHash = [BitConverter]::ToString($sha.ComputeHash([System.IO.File]::ReadAllBytes($target))).Replace('-', '').ToLowerInvariant() }
83
+ finally { $sha.Dispose() }
84
+ if ($actualHash -ne $installation.sha256) { throw '檔案內容已改變,請重新開啟該 VansPod 後重試移除。' }
85
+ Remove-Item -LiteralPath $target -Force
86
+ }
87
+ } elseif ($installation.kind -eq 'npm') {
88
+ $failedTarget = $installation.root
89
+ $prefix = [System.IO.Path]::GetFullPath($installation.prefix)
90
+ $packageRoot = [System.IO.Path]::GetFullPath($installation.root)
91
+ if ($packageRoot -ne (Join-Path $prefix 'node_modules/vanspod')) { throw 'Invalid npm registration' }
92
+ Assert-NoLinks $packageRoot
93
+ if (Test-Path -LiteralPath $packageRoot) {
94
+ $package = [System.IO.File]::ReadAllText((Join-Path $packageRoot 'package.json'), $utf8) | ConvertFrom-Json
95
+ if ($package.name -ne 'vanspod' -or $package.bin.vanspod -ne 'bin/vanspod.cjs') { throw 'Registered package is not VansPod' }
96
+ & $installation.node $installation.npmCli uninstall --global --prefix $prefix vanspod --ignore-scripts 2>&1 | Out-Null
97
+ if ($LASTEXITCODE -ne 0 -or (Test-Path -LiteralPath $packageRoot)) { throw 'npm 移除失敗。' }
98
+ }
99
+ } else { throw 'Unknown installation kind' }
100
+ }
101
+ foreach ($name in @('installations.json', 'uninstall-plan.json', 'uninstall-ready')) { $failedTarget = Join-Path $root $name; Remove-Owned $name }
102
+ $failedTarget = $PSCommandPath
103
+ Remove-Owned 'uninstall-tool.ps1'
104
+ if (-not @(Get-ChildItem -LiteralPath $root -Force).Count) { Remove-Item -LiteralPath $root -Force }
105
+ if (-not $planData.headless) { Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('VansPod 已完整移除。學生專案與獨立工具已保留;未登記的其他 exe 副本請自行刪除。', 'VansPod') | Out-Null }
106
+ } catch {
107
+ # A late cleanup error may have already removed a control file. Re-create
108
+ # the exact plan and tool so partial removal always remains retryable.
109
+ if (-not (Test-Path -LiteralPath $root)) { New-Item -ItemType Directory -Path $root | Out-Null }
110
+ Assert-NoLinks $root
111
+ [System.IO.File]::WriteAllText((Join-Path $root 'uninstall-plan.json'), $planSource, $utf8)
112
+ [System.IO.File]::WriteAllText((Join-Path $root 'uninstall-tool.ps1'), $toolSource, (New-Object System.Text.UTF8Encoding($true)))
113
+ $reason = $_.Exception.Message
114
+ $remaining = @($failedTarget)
115
+ foreach ($installation in $planData.installations) {
116
+ $location = if ($installation.kind -eq 'exe') { $installation.file } else { $installation.root }
117
+ if (Test-Path -LiteralPath $location) { $remaining += $location }
118
+ }
119
+ foreach ($name in @('runtime', 'desktop-assets', 'logs', 'installations.json', 'installations.lock', 'installations.json.tmp', 'uninstall-plan.json', 'uninstall-tool.ps1', 'uninstall-ready', 'startup-error.json', 'removal-result.json')) {
120
+ $location = Join-Path $root $name
121
+ if (Test-Path -LiteralPath $location) { $remaining += $location }
122
+ }
123
+ $legacyFile = Join-Path $localRoot 'classroom-app/connection.json'
124
+ if (Test-Path -LiteralPath $legacyFile) { $remaining += $legacyFile }
125
+ if (Test-Path -LiteralPath $dataRoot) {
126
+ foreach ($file in Get-ChildItem -LiteralPath $dataRoot -File -Force) { if ($file.Name -match '^(connection\.json|settings\.json|vscode-route-journal\.json)(\..*tmp)?$') { $remaining += $file.FullName } }
127
+ }
128
+ $remaining = @($remaining | Select-Object -Unique)
129
+ $message = "移除未完成:$reason`n剩餘項目:`n$($remaining -join "`n")`n請重試完整移除;若 VansPod 已重新啟動,請從畫面或 vanspod uninstall 重試。保留的 uninstall-tool.ps1 僅在服務已結束時可直接執行。"
130
+ [System.IO.File]::WriteAllText((Join-Path $root 'removal-result.json'), (@{ok = $false; message = $message; remaining = $remaining} | ConvertTo-Json), $utf8)
131
+ if (-not $planData.headless) { Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show($message, 'VansPod') | Out-Null }
132
+ exit 1
133
+ }