dsh-codex-subscription 0.2.8
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.
- package/AGENTS.md +150 -0
- package/LICENSE +21 -0
- package/README.en.md +180 -0
- package/README.md +166 -0
- package/SECURITY.md +32 -0
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/cordis.patch.yml +6 -0
- package/docs/assets/composer-quota-en.png +0 -0
- package/docs/assets/settings-en.png +0 -0
- package/docs/assets/settings.png +0 -0
- package/dsh-codex.ps1 +756 -0
- package/lib/client.js +865 -0
- package/lib/index.js +925 -0
- package/package.json +117 -0
package/dsh-codex.ps1
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[ValidateSet('Install', 'Update', 'Uninstall')]
|
|
4
|
+
[string] $Action = 'Install',
|
|
5
|
+
|
|
6
|
+
[ValidatePattern('^[A-Za-z0-9._-]+$')]
|
|
7
|
+
[string] $Profile = 'web',
|
|
8
|
+
|
|
9
|
+
[string] $PortableRoot,
|
|
10
|
+
|
|
11
|
+
[string] $CommandRoot,
|
|
12
|
+
|
|
13
|
+
[switch] $NoModifyPath,
|
|
14
|
+
|
|
15
|
+
[switch] $Managed,
|
|
16
|
+
|
|
17
|
+
[switch] $SkipSelfUpdate,
|
|
18
|
+
|
|
19
|
+
[switch] $DryRun
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
$ErrorActionPreference = 'Stop'
|
|
23
|
+
Set-StrictMode -Version 2.0
|
|
24
|
+
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
25
|
+
[Console]::OutputEncoding = $Utf8NoBom
|
|
26
|
+
$OutputEncoding = $Utf8NoBom
|
|
27
|
+
|
|
28
|
+
if ($Managed -and -not $PSBoundParameters.ContainsKey('Action')) {
|
|
29
|
+
Write-Host 'Usage: dsh-codex <install|update|uninstall>'
|
|
30
|
+
exit 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$PackageName = 'dsh-codex-subscription'
|
|
34
|
+
$LegacyPackageName = '@wsl043/dsh-codex-subscription'
|
|
35
|
+
$PackageVersion = '0.2.8'
|
|
36
|
+
$PackageSpec = 'https://github.com/WSL043/dsh-codex-subscription/releases/download/v0.2.8/dsh-codex-subscription.tgz'
|
|
37
|
+
$PnpmVersion = '11.19.0'
|
|
38
|
+
$PnpmUrl = 'https://registry.npmjs.org/pnpm/-/pnpm-11.19.0.tgz'
|
|
39
|
+
$PnpmSha512 = '7881F3ED590D472C4A955E2B88B2121791116066DCC88CBCA3849EC9B60F1BBAA6D2CCB221FA91DA4E1C65BEF2BCBE379365AEA7AC539C7BF86DEDC3A1B22DCE'
|
|
40
|
+
$ReleaseApi = if ($env:DSH_CODEX_RELEASE_API) { $env:DSH_CODEX_RELEASE_API } else { 'https://api.github.com/repos/WSL043/dsh-codex-subscription/releases/latest' }
|
|
41
|
+
$ReleaseBase = if ($env:DSH_CODEX_RELEASE_BASE) { $env:DSH_CODEX_RELEASE_BASE.TrimEnd('/') } else { 'https://github.com/WSL043/dsh-codex-subscription/releases/download' }
|
|
42
|
+
|
|
43
|
+
function Get-FileDigest {
|
|
44
|
+
param(
|
|
45
|
+
[Parameter(Mandatory = $true)][string] $Path,
|
|
46
|
+
[ValidateSet('SHA256', 'SHA512')][string] $Algorithm
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
$hasher = if ($Algorithm -eq 'SHA512') {
|
|
50
|
+
[System.Security.Cryptography.SHA512]::Create()
|
|
51
|
+
} else {
|
|
52
|
+
[System.Security.Cryptography.SHA256]::Create()
|
|
53
|
+
}
|
|
54
|
+
$stream = [System.IO.File]::OpenRead($Path)
|
|
55
|
+
try {
|
|
56
|
+
return ([System.BitConverter]::ToString($hasher.ComputeHash($stream))).Replace('-', '')
|
|
57
|
+
} finally {
|
|
58
|
+
$stream.Dispose()
|
|
59
|
+
$hasher.Dispose()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Get-ManagerCommandRoot {
|
|
64
|
+
if ($CommandRoot) { return Resolve-FullPath $CommandRoot }
|
|
65
|
+
if ($Managed -and $PSCommandPath) { return Split-Path -Parent (Resolve-FullPath $PSCommandPath) }
|
|
66
|
+
if (-not $env:LOCALAPPDATA) { throw 'LOCALAPPDATA is required to install the dsh-codex command.' }
|
|
67
|
+
return Join-Path $env:LOCALAPPDATA 'Programs\dsh-codex'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function Invoke-LatestManager {
|
|
71
|
+
param([Parameter(Mandatory = $true)][string] $InstalledCommandRoot)
|
|
72
|
+
|
|
73
|
+
Write-Host 'Checking the latest immutable release...'
|
|
74
|
+
$releaseResponse = Invoke-WebRequest -UseBasicParsing -Uri $ReleaseApi -Headers @{
|
|
75
|
+
Accept = 'application/vnd.github+json'
|
|
76
|
+
'User-Agent' = 'dsh-codex'
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
$release = $releaseResponse.Content | ConvertFrom-Json
|
|
80
|
+
} catch {
|
|
81
|
+
throw 'GitHub returned unreadable latest-release metadata.'
|
|
82
|
+
}
|
|
83
|
+
$tag = [string] $release.tag_name
|
|
84
|
+
if ($tag -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$') {
|
|
85
|
+
throw "GitHub returned an invalid release tag: $tag"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
$stage = Join-Path ([System.IO.Path]::GetTempPath()) ('dsh-codex-update-' + [guid]::NewGuid().ToString('N'))
|
|
89
|
+
$latestScript = Join-Path $stage 'dsh-codex.ps1'
|
|
90
|
+
$checksumFile = Join-Path $stage 'dsh-codex.ps1.sha256'
|
|
91
|
+
New-Item -ItemType Directory -Path $stage | Out-Null
|
|
92
|
+
try {
|
|
93
|
+
$assetBase = "$ReleaseBase/$tag"
|
|
94
|
+
Invoke-WebRequest -UseBasicParsing -Uri "$assetBase/dsh-codex.ps1" -OutFile $latestScript
|
|
95
|
+
Invoke-WebRequest -UseBasicParsing -Uri "$assetBase/dsh-codex.ps1.sha256" -OutFile $checksumFile
|
|
96
|
+
$checksumText = Get-Content -LiteralPath $checksumFile -Raw
|
|
97
|
+
$match = [regex]::Match($checksumText, '(?im)^\s*([a-f0-9]{64})\s+\*?dsh-codex\.ps1\s*$')
|
|
98
|
+
if (-not $match.Success) { throw 'The release manager checksum file is invalid.' }
|
|
99
|
+
$expectedHash = $match.Groups[1].Value.ToUpperInvariant()
|
|
100
|
+
$actualHash = Get-FileDigest -Algorithm SHA256 -Path $latestScript
|
|
101
|
+
if ($actualHash -ne $expectedHash) {
|
|
102
|
+
throw "Release manager checksum mismatch. Expected $expectedHash, received $actualHash."
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
$arguments = @(
|
|
106
|
+
'-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
|
|
107
|
+
'-File', $latestScript,
|
|
108
|
+
'-Managed', '-SkipSelfUpdate', '-Action', $Action,
|
|
109
|
+
'-Profile', $Profile,
|
|
110
|
+
'-CommandRoot', $InstalledCommandRoot
|
|
111
|
+
)
|
|
112
|
+
if ($PortableRoot) { $arguments += @('-PortableRoot', $PortableRoot) }
|
|
113
|
+
if ($NoModifyPath) { $arguments += '-NoModifyPath' }
|
|
114
|
+
& powershell.exe @arguments
|
|
115
|
+
if ($LASTEXITCODE -ne 0) { throw "The latest release manager failed with exit code $LASTEXITCODE." }
|
|
116
|
+
} finally {
|
|
117
|
+
if (Test-Path -LiteralPath $stage) {
|
|
118
|
+
Remove-Item -LiteralPath $stage -Recurse -Force -ErrorAction SilentlyContinue
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function Test-SamePath {
|
|
124
|
+
param(
|
|
125
|
+
[Parameter(Mandatory = $true)][string] $Left,
|
|
126
|
+
[Parameter(Mandatory = $true)][string] $Right
|
|
127
|
+
)
|
|
128
|
+
try {
|
|
129
|
+
$leftPath = (Resolve-FullPath $Left).TrimEnd('\')
|
|
130
|
+
$rightPath = (Resolve-FullPath $Right).TrimEnd('\')
|
|
131
|
+
return [string]::Equals($leftPath, $rightPath, [System.StringComparison]::OrdinalIgnoreCase)
|
|
132
|
+
} catch {
|
|
133
|
+
return [string]::Equals($Left.Trim(), $Right.Trim(), [System.StringComparison]::OrdinalIgnoreCase)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function Add-UserPathEntry {
|
|
138
|
+
param(
|
|
139
|
+
[AllowNull()][AllowEmptyString()][string] $UserPath,
|
|
140
|
+
[Parameter(Mandatory = $true)][string] $Directory
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
foreach ($entry in @(([string] $UserPath).Split(';'))) {
|
|
144
|
+
if ($entry.Trim() -and (Test-SamePath -Left $entry -Right $Directory)) {
|
|
145
|
+
return [string] $UserPath
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (-not $UserPath) { return $Directory }
|
|
149
|
+
|
|
150
|
+
# Always add our own separator. If the existing value already ends in one,
|
|
151
|
+
# removing this suffix can still restore the original text byte-for-byte.
|
|
152
|
+
return $UserPath + ';' + $Directory
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Remove-UserPathEntry {
|
|
156
|
+
param(
|
|
157
|
+
[AllowNull()][AllowEmptyString()][string] $UserPath,
|
|
158
|
+
[Parameter(Mandatory = $true)][string] $Directory
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if (-not $UserPath) { return [string] $UserPath }
|
|
162
|
+
|
|
163
|
+
$lastSeparator = $UserPath.LastIndexOf(';')
|
|
164
|
+
if ($lastSeparator -ge 0) {
|
|
165
|
+
$lastEntry = $UserPath.Substring($lastSeparator + 1)
|
|
166
|
+
if ($lastEntry.Trim() -and (Test-SamePath -Left $lastEntry -Right $Directory)) {
|
|
167
|
+
return $UserPath.Substring(0, $lastSeparator)
|
|
168
|
+
}
|
|
169
|
+
} elseif (Test-SamePath -Left $UserPath -Right $Directory) {
|
|
170
|
+
return ''
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
$kept = New-Object System.Collections.Generic.List[string]
|
|
174
|
+
foreach ($entry in $UserPath.Split(';')) {
|
|
175
|
+
if ($entry.Trim() -and (Test-SamePath -Left $entry -Right $Directory)) { continue }
|
|
176
|
+
$kept.Add($entry)
|
|
177
|
+
}
|
|
178
|
+
return $kept -join ';'
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function Publish-UserPathChange {
|
|
182
|
+
try {
|
|
183
|
+
if (-not ('DshCodex.NativeMethods' -as [type])) {
|
|
184
|
+
Add-Type -TypeDefinition @'
|
|
185
|
+
using System;
|
|
186
|
+
using System.Runtime.InteropServices;
|
|
187
|
+
namespace DshCodex {
|
|
188
|
+
public static class NativeMethods {
|
|
189
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
190
|
+
public static extern IntPtr SendMessageTimeout(
|
|
191
|
+
IntPtr hWnd,
|
|
192
|
+
uint message,
|
|
193
|
+
UIntPtr wParam,
|
|
194
|
+
string lParam,
|
|
195
|
+
uint flags,
|
|
196
|
+
uint timeout,
|
|
197
|
+
out UIntPtr result);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
'@
|
|
201
|
+
}
|
|
202
|
+
[UIntPtr] $result = [UIntPtr]::Zero
|
|
203
|
+
# HWND_BROADCAST + WM_SETTINGCHANGE with SMTO_ABORTIFHUNG.
|
|
204
|
+
[void] [DshCodex.NativeMethods]::SendMessageTimeout(
|
|
205
|
+
[IntPtr] 0xffff,
|
|
206
|
+
0x001A,
|
|
207
|
+
[UIntPtr]::Zero,
|
|
208
|
+
'Environment',
|
|
209
|
+
0x0002,
|
|
210
|
+
2000,
|
|
211
|
+
[ref] $result
|
|
212
|
+
)
|
|
213
|
+
} catch {
|
|
214
|
+
Write-Warning 'The user PATH was updated, but Windows did not accept the environment refresh notification.'
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function Add-ManagerToUserPath {
|
|
219
|
+
param([Parameter(Mandatory = $true)][string] $Directory)
|
|
220
|
+
|
|
221
|
+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
222
|
+
$updated = Add-UserPathEntry -UserPath $userPath -Directory $Directory
|
|
223
|
+
if ([string]::Equals([string] $updated, [string] $userPath, [System.StringComparison]::Ordinal)) { return }
|
|
224
|
+
[Environment]::SetEnvironmentVariable('Path', $updated, 'User')
|
|
225
|
+
Publish-UserPathChange
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function Remove-ManagerFromUserPath {
|
|
229
|
+
param([Parameter(Mandatory = $true)][string] $Directory)
|
|
230
|
+
|
|
231
|
+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
232
|
+
if (-not $userPath) { return }
|
|
233
|
+
$updated = Remove-UserPathEntry -UserPath $userPath -Directory $Directory
|
|
234
|
+
if ([string]::Equals([string] $updated, [string] $userPath, [System.StringComparison]::Ordinal)) { return }
|
|
235
|
+
[Environment]::SetEnvironmentVariable('Path', $updated, 'User')
|
|
236
|
+
Publish-UserPathChange
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function Start-ManagerCleanup {
|
|
240
|
+
param([Parameter(Mandatory = $true)][string] $Directory)
|
|
241
|
+
|
|
242
|
+
$installedScript = Join-Path $Directory 'dsh-codex.ps1'
|
|
243
|
+
$installedShim = Join-Path $Directory 'dsh-codex.cmd'
|
|
244
|
+
if (-not (Test-Path -LiteralPath $installedScript -PathType Leaf) -or
|
|
245
|
+
-not (Test-Path -LiteralPath $installedShim -PathType Leaf)) {
|
|
246
|
+
throw 'Refusing to clean an unrecognized manager command directory.'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
$cleanupScript = Join-Path ([System.IO.Path]::GetTempPath()) ('dsh-codex-cleanup-' + [guid]::NewGuid().ToString('N') + '.ps1')
|
|
250
|
+
$cleanup = @'
|
|
251
|
+
param(
|
|
252
|
+
[int] $ParentId,
|
|
253
|
+
[string] $DirectoryBase64,
|
|
254
|
+
[string] $CleanupBase64
|
|
255
|
+
)
|
|
256
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
257
|
+
$directory = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($DirectoryBase64))
|
|
258
|
+
$cleanupScript = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($CleanupBase64))
|
|
259
|
+
try {
|
|
260
|
+
Wait-Process -Id $ParentId -ErrorAction SilentlyContinue
|
|
261
|
+
Start-Sleep -Milliseconds 300
|
|
262
|
+
Remove-Item -LiteralPath (Join-Path $directory 'dsh-codex.ps1') -Force -ErrorAction SilentlyContinue
|
|
263
|
+
Remove-Item -LiteralPath (Join-Path $directory 'dsh-codex.cmd') -Force -ErrorAction SilentlyContinue
|
|
264
|
+
if ((Test-Path -LiteralPath $directory -PathType Container) -and
|
|
265
|
+
-not (Get-ChildItem -LiteralPath $directory -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) {
|
|
266
|
+
Remove-Item -LiteralPath $directory -Force -ErrorAction SilentlyContinue
|
|
267
|
+
}
|
|
268
|
+
} finally {
|
|
269
|
+
Remove-Item -LiteralPath $cleanupScript -Force -ErrorAction SilentlyContinue
|
|
270
|
+
}
|
|
271
|
+
'@
|
|
272
|
+
[System.IO.File]::WriteAllText($cleanupScript, $cleanup, $Utf8NoBom)
|
|
273
|
+
$directoryBase64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Directory))
|
|
274
|
+
$cleanupBase64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cleanupScript))
|
|
275
|
+
$argumentLine = "-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$cleanupScript`" -ParentId $PID -DirectoryBase64 $directoryBase64 -CleanupBase64 $cleanupBase64"
|
|
276
|
+
Start-Process -FilePath 'powershell.exe' -ArgumentList $argumentLine -WindowStyle Hidden | Out-Null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function Remove-ManagerCommand {
|
|
280
|
+
param([Parameter(Mandatory = $true)][string] $Directory)
|
|
281
|
+
|
|
282
|
+
if (-not $NoModifyPath) { Remove-ManagerFromUserPath -Directory $Directory }
|
|
283
|
+
if (Test-Path -LiteralPath $Directory -PathType Container) {
|
|
284
|
+
$installedScript = Join-Path $Directory 'dsh-codex.ps1'
|
|
285
|
+
if ($Managed -and $PSCommandPath -and (Test-SamePath -Left $PSCommandPath -Right $installedScript)) {
|
|
286
|
+
Start-ManagerCleanup -Directory $Directory
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
foreach ($ownedFile in @('dsh-codex.ps1', 'dsh-codex.cmd')) {
|
|
290
|
+
$ownedPath = Join-Path $Directory $ownedFile
|
|
291
|
+
if (Test-Path -LiteralPath $ownedPath -PathType Leaf) {
|
|
292
|
+
Remove-Item -LiteralPath $ownedPath -Force
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (-not (Get-ChildItem -LiteralPath $Directory -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) {
|
|
296
|
+
Remove-Item -LiteralPath $Directory -Force
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function Install-ManagerCommand {
|
|
302
|
+
param([Parameter(Mandatory = $true)][string] $Directory)
|
|
303
|
+
|
|
304
|
+
if (-not $PSCommandPath -or -not (Test-Path -LiteralPath $PSCommandPath -PathType Leaf)) {
|
|
305
|
+
throw 'The manager command can only be installed from a downloaded script file.'
|
|
306
|
+
}
|
|
307
|
+
New-Item -ItemType Directory -Force -Path $Directory | Out-Null
|
|
308
|
+
$installedScript = Join-Path $Directory 'dsh-codex.ps1'
|
|
309
|
+
if (-not (Test-SamePath -Left $PSCommandPath -Right $installedScript)) {
|
|
310
|
+
$stagedScript = Join-Path $Directory ('.dsh-codex-' + [guid]::NewGuid().ToString('N') + '.ps1')
|
|
311
|
+
try {
|
|
312
|
+
Copy-Item -LiteralPath $PSCommandPath -Destination $stagedScript
|
|
313
|
+
Move-Item -LiteralPath $stagedScript -Destination $installedScript -Force
|
|
314
|
+
} finally {
|
|
315
|
+
if (Test-Path -LiteralPath $stagedScript) {
|
|
316
|
+
Remove-Item -LiteralPath $stagedScript -Force -ErrorAction SilentlyContinue
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
$shim = @'
|
|
322
|
+
@echo off
|
|
323
|
+
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0dsh-codex.ps1" -Managed %*
|
|
324
|
+
exit /b %ERRORLEVEL%
|
|
325
|
+
'@
|
|
326
|
+
[System.IO.File]::WriteAllText((Join-Path $Directory 'dsh-codex.cmd'), $shim, [System.Text.Encoding]::ASCII)
|
|
327
|
+
if (-not $NoModifyPath) { Add-ManagerToUserPath -Directory $Directory }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function Resolve-FullPath {
|
|
331
|
+
param([Parameter(Mandatory = $true)][string] $Path)
|
|
332
|
+
return [System.IO.Path]::GetFullPath([Environment]::ExpandEnvironmentVariables($Path))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function Get-PortableLayout {
|
|
336
|
+
param([Parameter(Mandatory = $true)][string] $Root)
|
|
337
|
+
|
|
338
|
+
$resolvedRoot = Resolve-FullPath $Root
|
|
339
|
+
$node = Join-Path $resolvedRoot 'runtime\node\node.exe'
|
|
340
|
+
$dsh = Join-Path $resolvedRoot 'app\node_modules\@deepseek-ai\dsh\lib\bin.js'
|
|
341
|
+
$portableCli = Join-Path $resolvedRoot 'dsh.exe'
|
|
342
|
+
if (-not (Test-Path -LiteralPath $node -PathType Leaf) -or
|
|
343
|
+
-not (Test-Path -LiteralPath $dsh -PathType Leaf)) {
|
|
344
|
+
return $null
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
$stateRoot = $resolvedRoot
|
|
348
|
+
$installedMode = Join-Path $resolvedRoot 'installed-mode.json'
|
|
349
|
+
if (Test-Path -LiteralPath $installedMode -PathType Leaf) {
|
|
350
|
+
$mode = Get-Content -LiteralPath $installedMode -Raw | ConvertFrom-Json
|
|
351
|
+
if (-not $mode.stateRoot) { throw "Invalid installed-mode.json: stateRoot is missing." }
|
|
352
|
+
$stateRoot = Resolve-FullPath ([string] $mode.stateRoot)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return [pscustomobject]@{
|
|
356
|
+
Root = $resolvedRoot
|
|
357
|
+
StateRoot = $stateRoot
|
|
358
|
+
Node = $node
|
|
359
|
+
Dsh = $dsh
|
|
360
|
+
PortableCli = if (Test-Path -LiteralPath $portableCli -PathType Leaf) { $portableCli } else { $null }
|
|
361
|
+
DshHome = Join-Path $stateRoot 'data\dsh-home'
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function New-PortableTarget {
|
|
366
|
+
param([Parameter(Mandatory = $true)] $Layout)
|
|
367
|
+
$usesPortableCli = $null -ne $Layout.PortableCli
|
|
368
|
+
return [pscustomobject]@{
|
|
369
|
+
Mode = 'portable'
|
|
370
|
+
Layout = $Layout
|
|
371
|
+
Executable = if ($usesPortableCli) { $Layout.PortableCli } else { $Layout.Node }
|
|
372
|
+
Node = $Layout.Node
|
|
373
|
+
UsesPortableCli = $usesPortableCli
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function Find-PortableFromCurrentDirectory {
|
|
378
|
+
$directory = [System.IO.DirectoryInfo]::new((Get-Location).Path)
|
|
379
|
+
while ($null -ne $directory) {
|
|
380
|
+
$layout = Get-PortableLayout $directory.FullName
|
|
381
|
+
if ($null -ne $layout) { return $layout }
|
|
382
|
+
$directory = $directory.Parent
|
|
383
|
+
}
|
|
384
|
+
return $null
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function Find-RunningPortables {
|
|
388
|
+
try {
|
|
389
|
+
$processes = Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" -ErrorAction Stop
|
|
390
|
+
foreach ($process in $processes) {
|
|
391
|
+
$executable = [string] $process.ExecutablePath
|
|
392
|
+
if (-not $executable) { continue }
|
|
393
|
+
$nodeDirectory = Split-Path -Parent $executable
|
|
394
|
+
$runtimeDirectory = Split-Path -Parent $nodeDirectory
|
|
395
|
+
$root = Split-Path -Parent $runtimeDirectory
|
|
396
|
+
$layout = Get-PortableLayout $root
|
|
397
|
+
if ($null -eq $layout) { continue }
|
|
398
|
+
if (-not [string]::Equals(
|
|
399
|
+
(Resolve-FullPath $executable),
|
|
400
|
+
$layout.Node,
|
|
401
|
+
[System.StringComparison]::OrdinalIgnoreCase
|
|
402
|
+
)) { continue }
|
|
403
|
+
$commandLine = [string] $process.CommandLine
|
|
404
|
+
if ($commandLine -match '(?i)@deepseek-ai[\\/]dsh[\\/]lib[\\/]bin\.js') {
|
|
405
|
+
Write-Output $layout
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
# Process discovery is only one optional location hint.
|
|
410
|
+
}
|
|
411
|
+
return $null
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function Find-CommonPortables {
|
|
415
|
+
$candidates = New-Object System.Collections.Generic.List[string]
|
|
416
|
+
if ($env:LOCALAPPDATA) {
|
|
417
|
+
$candidates.Add((Join-Path $env:LOCALAPPDATA 'Programs\DeepSeek-Herness'))
|
|
418
|
+
}
|
|
419
|
+
if ($env:USERPROFILE) {
|
|
420
|
+
$candidates.Add((Join-Path $env:USERPROFILE 'Downloads\DSH-Portable'))
|
|
421
|
+
$candidates.Add((Join-Path $env:USERPROFILE 'Desktop\DSH-Portable'))
|
|
422
|
+
foreach ($parent in @(
|
|
423
|
+
(Join-Path $env:USERPROFILE 'Downloads'),
|
|
424
|
+
(Join-Path $env:USERPROFILE 'Desktop')
|
|
425
|
+
)) {
|
|
426
|
+
if (Test-Path -LiteralPath $parent -PathType Container) {
|
|
427
|
+
foreach ($directory in Get-ChildItem -LiteralPath $parent -Directory -ErrorAction SilentlyContinue) {
|
|
428
|
+
$candidates.Add($directory.FullName)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
foreach ($candidate in $candidates | Select-Object -Unique) {
|
|
435
|
+
$layout = Get-PortableLayout $candidate
|
|
436
|
+
if ($null -ne $layout) { Write-Output $layout }
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function Get-ManagerTarget {
|
|
441
|
+
if ($PortableRoot) {
|
|
442
|
+
$layout = Get-PortableLayout $PortableRoot
|
|
443
|
+
if ($null -eq $layout) {
|
|
444
|
+
throw "The selected DSH-Portable folder is incomplete: $PortableRoot"
|
|
445
|
+
}
|
|
446
|
+
return New-PortableTarget $layout
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
$layout = Find-PortableFromCurrentDirectory
|
|
450
|
+
if ($null -ne $layout) {
|
|
451
|
+
return New-PortableTarget $layout
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
$runningLayouts = @(Find-RunningPortables | Sort-Object -Property Root -Unique)
|
|
455
|
+
if ($runningLayouts.Count -gt 1) {
|
|
456
|
+
$paths = ($runningLayouts | ForEach-Object { "- $($_.Root)" }) -join [Environment]::NewLine
|
|
457
|
+
throw "More than one running DSH-Portable was found. Re-run with -PortableRoot:`n$paths"
|
|
458
|
+
}
|
|
459
|
+
if ($runningLayouts.Count -eq 1) {
|
|
460
|
+
$layout = $runningLayouts[0]
|
|
461
|
+
return New-PortableTarget $layout
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
$globalDsh = Get-Command dsh -ErrorAction SilentlyContinue
|
|
465
|
+
$commonLayouts = @(Find-CommonPortables | Sort-Object -Property Root -Unique)
|
|
466
|
+
if ($commonLayouts.Count -gt 1) {
|
|
467
|
+
$paths = ($commonLayouts | ForEach-Object { "- $($_.Root)" }) -join [Environment]::NewLine
|
|
468
|
+
throw "More than one DSH-Portable folder was found. Re-run with -PortableRoot:`n$paths"
|
|
469
|
+
}
|
|
470
|
+
if ($null -ne $globalDsh -and $commonLayouts.Count -eq 1) {
|
|
471
|
+
throw "Both a global dsh command and DSH-Portable were found. Run from the intended portable folder, or pass -PortableRoot '$($commonLayouts[0].Root)'."
|
|
472
|
+
}
|
|
473
|
+
if ($commonLayouts.Count -eq 1) {
|
|
474
|
+
$layout = $commonLayouts[0]
|
|
475
|
+
return New-PortableTarget $layout
|
|
476
|
+
}
|
|
477
|
+
if ($null -ne $globalDsh) {
|
|
478
|
+
$globalNode = Get-Command node -ErrorAction SilentlyContinue
|
|
479
|
+
if ($null -eq $globalNode) { throw 'The dsh command exists, but Node.js is not available on PATH.' }
|
|
480
|
+
return [pscustomobject]@{ Mode = 'global'; Layout = $null; Executable = $globalDsh.Source; Node = $globalNode.Source; UsesPortableCli = $false }
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
throw @'
|
|
484
|
+
DeepSeek Harness was not found.
|
|
485
|
+
|
|
486
|
+
If you use DSH-Portable, start it once or run this command from inside its
|
|
487
|
+
folder. You can also pass -PortableRoot "C:\path\to\DSH-Portable".
|
|
488
|
+
'@
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function Get-PnpmDirectory {
|
|
492
|
+
param([Parameter(Mandatory = $true)] $Target)
|
|
493
|
+
if ($Target.UsesPortableCli) { return $null }
|
|
494
|
+
if ($Target.Mode -eq 'portable') {
|
|
495
|
+
return Join-Path $Target.Layout.StateRoot "data\runtime\dsh-codex-tools\pnpm-$PnpmVersion"
|
|
496
|
+
}
|
|
497
|
+
if (-not $env:LOCALAPPDATA) { throw 'LOCALAPPDATA is required to cache the plugin manager.' }
|
|
498
|
+
return Join-Path $env:LOCALAPPDATA "dsh-codex-subscription\tools\pnpm-$PnpmVersion"
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function Get-PnpmStore {
|
|
502
|
+
param([Parameter(Mandatory = $true)] $Target)
|
|
503
|
+
if ($Target.Mode -eq 'portable') {
|
|
504
|
+
return Join-Path $Target.Layout.StateRoot 'data\pnpm-store'
|
|
505
|
+
}
|
|
506
|
+
return $null
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function Test-PnpmDirectory {
|
|
510
|
+
param(
|
|
511
|
+
[Parameter(Mandatory = $true)][string] $Directory,
|
|
512
|
+
[Parameter(Mandatory = $true)][string] $Node
|
|
513
|
+
)
|
|
514
|
+
$entry = Join-Path $Directory 'package\bin\pnpm.cjs'
|
|
515
|
+
if (-not (Test-Path -LiteralPath $entry -PathType Leaf)) { return $false }
|
|
516
|
+
try {
|
|
517
|
+
$reported = (& $Node $entry '--version' 2>$null | Select-Object -First 1)
|
|
518
|
+
return ([string] $reported).Trim() -eq $PnpmVersion
|
|
519
|
+
} catch {
|
|
520
|
+
return $false
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function Install-PnpmTool {
|
|
525
|
+
param(
|
|
526
|
+
[Parameter(Mandatory = $true)][string] $Directory,
|
|
527
|
+
[Parameter(Mandatory = $true)][string] $Node
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if (Test-PnpmDirectory -Directory $Directory -Node $Node) { return }
|
|
531
|
+
|
|
532
|
+
$parent = Split-Path -Parent $Directory
|
|
533
|
+
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
|
534
|
+
$stage = Join-Path $parent ('.pnpm-' + [guid]::NewGuid().ToString('N'))
|
|
535
|
+
$archive = Join-Path $stage 'pnpm.tgz'
|
|
536
|
+
New-Item -ItemType Directory -Path $stage | Out-Null
|
|
537
|
+
try {
|
|
538
|
+
Write-Host "Preparing the bundled plugin manager (pnpm $PnpmVersion)..."
|
|
539
|
+
Invoke-WebRequest -UseBasicParsing -Uri $PnpmUrl -OutFile $archive
|
|
540
|
+
$actualHash = Get-FileDigest -Algorithm SHA512 -Path $archive
|
|
541
|
+
if ($actualHash -ne $PnpmSha512) {
|
|
542
|
+
throw "pnpm download checksum mismatch. Expected $PnpmSha512, received $actualHash."
|
|
543
|
+
}
|
|
544
|
+
$tar = Get-Command tar.exe -ErrorAction SilentlyContinue
|
|
545
|
+
if ($null -eq $tar) { throw 'Windows tar.exe is required to unpack the verified pnpm package.' }
|
|
546
|
+
& $tar.Source '-xzf' $archive '-C' $stage
|
|
547
|
+
if ($LASTEXITCODE -ne 0) { throw "tar.exe failed with exit code $LASTEXITCODE." }
|
|
548
|
+
Remove-Item -LiteralPath $archive -Force
|
|
549
|
+
if (-not (Test-PnpmDirectory -Directory $stage -Node $Node)) {
|
|
550
|
+
throw "The extracted pnpm package did not report version $PnpmVersion."
|
|
551
|
+
}
|
|
552
|
+
$shim = "@echo off`r`n`"%DSH_CODEX_NODE%`" `"%~dp0package\bin\pnpm.cjs`" %*`r`n"
|
|
553
|
+
[System.IO.File]::WriteAllText((Join-Path $stage 'pnpm.cmd'), $shim, [System.Text.Encoding]::ASCII)
|
|
554
|
+
if (Test-Path -LiteralPath $Directory) {
|
|
555
|
+
Remove-Item -LiteralPath $Directory -Recurse -Force
|
|
556
|
+
}
|
|
557
|
+
Move-Item -LiteralPath $stage -Destination $Directory
|
|
558
|
+
} finally {
|
|
559
|
+
if (Test-Path -LiteralPath $stage) {
|
|
560
|
+
Remove-Item -LiteralPath $stage -Recurse -Force -ErrorAction SilentlyContinue
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function Get-ActionArguments {
|
|
566
|
+
param(
|
|
567
|
+
[Parameter(Mandatory = $true)][string] $SelectedAction,
|
|
568
|
+
[AllowNull()][string] $Store,
|
|
569
|
+
[string] $SelectedPackage = $PackageName
|
|
570
|
+
)
|
|
571
|
+
if ($SelectedAction -eq 'Uninstall') {
|
|
572
|
+
$arguments = @('plugin', '--profile', $Profile, 'remove', $SelectedPackage)
|
|
573
|
+
} else {
|
|
574
|
+
$arguments = @('plugin', '--profile', $Profile, 'add', $PackageSpec)
|
|
575
|
+
}
|
|
576
|
+
if ($Store) { $arguments += @('--store-dir', $Store) }
|
|
577
|
+
$arguments += @('--loglevel', 'error')
|
|
578
|
+
return $arguments
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function Invoke-DshCommand {
|
|
582
|
+
param(
|
|
583
|
+
[Parameter(Mandatory = $true)] $Target,
|
|
584
|
+
[Parameter(Mandatory = $true)][string[]] $Arguments,
|
|
585
|
+
[switch] $Capture
|
|
586
|
+
)
|
|
587
|
+
$allArguments = if ($Target.Mode -eq 'portable' -and -not $Target.UsesPortableCli) {
|
|
588
|
+
@($Target.Layout.Dsh) + $Arguments
|
|
589
|
+
} else {
|
|
590
|
+
$Arguments
|
|
591
|
+
}
|
|
592
|
+
if ($Capture) {
|
|
593
|
+
$output = & $Target.Executable @allArguments 2>&1
|
|
594
|
+
$exitCode = $LASTEXITCODE
|
|
595
|
+
if ($exitCode -ne 0) { throw "dsh failed with exit code $exitCode.`n$($output -join [Environment]::NewLine)" }
|
|
596
|
+
return ($output -join [Environment]::NewLine)
|
|
597
|
+
}
|
|
598
|
+
& $Target.Executable @allArguments
|
|
599
|
+
if ($LASTEXITCODE -ne 0) { throw "dsh failed with exit code $LASTEXITCODE." }
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function Get-InstalledPackageNames {
|
|
603
|
+
param([Parameter(Mandatory = $true)] $Target)
|
|
604
|
+
|
|
605
|
+
$json = Invoke-DshCommand -Target $Target -Arguments @(
|
|
606
|
+
'plugin', '--profile', $Profile, 'list', '--depth', '0', '--json', '--loglevel', 'error'
|
|
607
|
+
) -Capture
|
|
608
|
+
try {
|
|
609
|
+
# Windows PowerShell 5.1 preserves a top-level JSON array as one pipeline
|
|
610
|
+
# object. Assign first so @() expands the resulting Object[] correctly.
|
|
611
|
+
$parsedProjects = $json | ConvertFrom-Json
|
|
612
|
+
$projects = @($parsedProjects)
|
|
613
|
+
} catch {
|
|
614
|
+
throw 'DSH returned an unreadable plugin list.'
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
foreach ($project in $projects) {
|
|
618
|
+
$dependenciesProperty = $project.PSObject.Properties['dependencies']
|
|
619
|
+
if ($null -eq $dependenciesProperty -or $null -eq $dependenciesProperty.Value) { continue }
|
|
620
|
+
foreach ($property in $dependenciesProperty.Value.PSObject.Properties) {
|
|
621
|
+
Write-Output ([string] $property.Name)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
$managerCommandRoot = Get-ManagerCommandRoot
|
|
627
|
+
if ($Managed -and $Action -eq 'Update' -and -not $SkipSelfUpdate -and -not $DryRun) {
|
|
628
|
+
Invoke-LatestManager -InstalledCommandRoot $managerCommandRoot
|
|
629
|
+
exit 0
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
$target = Get-ManagerTarget
|
|
633
|
+
$managerCommand = Join-Path $managerCommandRoot 'dsh-codex.cmd'
|
|
634
|
+
$pnpmDirectory = Get-PnpmDirectory $target
|
|
635
|
+
$pnpmStore = Get-PnpmStore $target
|
|
636
|
+
$actionArguments = Get-ActionArguments -SelectedAction $Action -Store $pnpmStore
|
|
637
|
+
$arguments = if ($target.Mode -eq 'portable' -and -not $target.UsesPortableCli) {
|
|
638
|
+
@($target.Layout.Dsh) + $actionArguments
|
|
639
|
+
} else {
|
|
640
|
+
$actionArguments
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if ($DryRun) {
|
|
644
|
+
[ordered]@{
|
|
645
|
+
mode = $target.Mode
|
|
646
|
+
action = $Action
|
|
647
|
+
executable = $target.Executable
|
|
648
|
+
arguments = $arguments
|
|
649
|
+
dshHome = if ($target.Mode -eq 'portable') { $target.Layout.DshHome } else { $null }
|
|
650
|
+
pnpmVersion = $PnpmVersion
|
|
651
|
+
pnpmDirectory = $pnpmDirectory
|
|
652
|
+
pnpmStore = $pnpmStore
|
|
653
|
+
packageName = $PackageName
|
|
654
|
+
legacyPackageName = $LegacyPackageName
|
|
655
|
+
packageVersion = $PackageVersion
|
|
656
|
+
packageSpec = $PackageSpec
|
|
657
|
+
managerCommand = $managerCommand
|
|
658
|
+
installsManagerCommand = $Action -ne 'Uninstall'
|
|
659
|
+
modifiesUserPath = ($Action -ne 'Uninstall') -and (-not $NoModifyPath)
|
|
660
|
+
removesProfile = $false
|
|
661
|
+
} | ConvertTo-Json -Depth 4 -Compress
|
|
662
|
+
exit 0
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
$oldPath = $env:PATH
|
|
666
|
+
$oldDshHome = $env:DSH_HOME
|
|
667
|
+
$oldDshPortable = $env:DSH_PORTABLE
|
|
668
|
+
$oldTelemetry = $env:DSH_TELEMETRY_MODE
|
|
669
|
+
$oldManagerNode = $env:DSH_CODEX_NODE
|
|
670
|
+
$oldPnpmStore = $env:npm_config_store_dir
|
|
671
|
+
$oldPnpmNotifier = $env:npm_config_update_notifier
|
|
672
|
+
try {
|
|
673
|
+
if (-not $target.UsesPortableCli) {
|
|
674
|
+
Install-PnpmTool -Directory $pnpmDirectory -Node $target.Node
|
|
675
|
+
}
|
|
676
|
+
$env:DSH_CODEX_NODE = $target.Node
|
|
677
|
+
if (-not $target.UsesPortableCli) {
|
|
678
|
+
$env:PATH = $pnpmDirectory + [System.IO.Path]::PathSeparator + (Split-Path -Parent $target.Node) + [System.IO.Path]::PathSeparator + $oldPath
|
|
679
|
+
}
|
|
680
|
+
$env:npm_config_update_notifier = 'false'
|
|
681
|
+
if ($target.Mode -eq 'portable') {
|
|
682
|
+
New-Item -ItemType Directory -Force -Path $target.Layout.DshHome | Out-Null
|
|
683
|
+
$env:DSH_HOME = $target.Layout.DshHome
|
|
684
|
+
$env:DSH_PORTABLE = '1'
|
|
685
|
+
$env:DSH_TELEMETRY_MODE = 'DISABLED'
|
|
686
|
+
$env:npm_config_store_dir = $pnpmStore
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
$installedBefore = @(Get-InstalledPackageNames -Target $target)
|
|
690
|
+
$hadPackage = $installedBefore -contains $PackageName
|
|
691
|
+
$hadLegacyPackage = $installedBefore -contains $LegacyPackageName
|
|
692
|
+
|
|
693
|
+
if ($Action -eq 'Uninstall') {
|
|
694
|
+
if ($hadPackage) {
|
|
695
|
+
Invoke-DshCommand -Target $target -Arguments (
|
|
696
|
+
Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $PackageName
|
|
697
|
+
)
|
|
698
|
+
}
|
|
699
|
+
if ($hadLegacyPackage) {
|
|
700
|
+
Invoke-DshCommand -Target $target -Arguments (
|
|
701
|
+
Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $LegacyPackageName
|
|
702
|
+
)
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
Invoke-DshCommand -Target $target -Arguments $actionArguments
|
|
706
|
+
if ($hadLegacyPackage) {
|
|
707
|
+
try {
|
|
708
|
+
Invoke-DshCommand -Target $target -Arguments (
|
|
709
|
+
Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $LegacyPackageName
|
|
710
|
+
)
|
|
711
|
+
} catch {
|
|
712
|
+
if (-not $hadPackage) {
|
|
713
|
+
try {
|
|
714
|
+
Invoke-DshCommand -Target $target -Arguments (
|
|
715
|
+
Get-ActionArguments -SelectedAction 'Uninstall' -Store $pnpmStore -SelectedPackage $PackageName
|
|
716
|
+
)
|
|
717
|
+
} catch {
|
|
718
|
+
# Preserve the original migration error.
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
throw
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
$config = Invoke-DshCommand -Target $target -Arguments @('--profile', $Profile, '--dump-config') -Capture
|
|
727
|
+
$entryCount = ([regex]::Matches($config, '(?<![A-Za-z0-9_-])codex-subscription(?![A-Za-z0-9_-])')).Count
|
|
728
|
+
$legacyEntryCount = ([regex]::Matches($config, '(?<![A-Za-z0-9_-])wsl043-codex-subscription(?![A-Za-z0-9_-])')).Count
|
|
729
|
+
$installedAfter = @(Get-InstalledPackageNames -Target $target)
|
|
730
|
+
if ($Action -eq 'Uninstall') {
|
|
731
|
+
if (($installedAfter -contains $PackageName) -or ($installedAfter -contains $LegacyPackageName)) {
|
|
732
|
+
throw 'The plugin package is still present after uninstall.'
|
|
733
|
+
}
|
|
734
|
+
if ($entryCount -ne 0 -or $legacyEntryCount -ne 0) {
|
|
735
|
+
throw 'The plugin package was removed, but its profile entry is still present.'
|
|
736
|
+
}
|
|
737
|
+
Remove-ManagerCommand -Directory $managerCommandRoot
|
|
738
|
+
Write-Host 'Uninstalled. The DSH profile and saved credentials were kept.'
|
|
739
|
+
} else {
|
|
740
|
+
if (-not ($installedAfter -contains $PackageName)) { throw 'The installed package did not appear in the DSH plugin list.' }
|
|
741
|
+
if ($installedAfter -contains $LegacyPackageName) { throw 'The legacy package is still present after migration.' }
|
|
742
|
+
if ($entryCount -ne 1) { throw "Expected one plugin profile entry, found $entryCount." }
|
|
743
|
+
if ($legacyEntryCount -ne 0) { throw 'The legacy plugin profile entry is still present after migration.' }
|
|
744
|
+
Install-ManagerCommand -Directory $managerCommandRoot
|
|
745
|
+
$verb = if ($Action -eq 'Update') { 'Updated.' } else { 'Installed.' }
|
|
746
|
+
Write-Host "$verb Restart DSH manually to load the change if it is currently running."
|
|
747
|
+
}
|
|
748
|
+
} finally {
|
|
749
|
+
$env:PATH = $oldPath
|
|
750
|
+
$env:DSH_HOME = $oldDshHome
|
|
751
|
+
$env:DSH_PORTABLE = $oldDshPortable
|
|
752
|
+
$env:DSH_TELEMETRY_MODE = $oldTelemetry
|
|
753
|
+
$env:DSH_CODEX_NODE = $oldManagerNode
|
|
754
|
+
$env:npm_config_store_dir = $oldPnpmStore
|
|
755
|
+
$env:npm_config_update_notifier = $oldPnpmNotifier
|
|
756
|
+
}
|