patchwarden 0.6.0 → 0.6.4
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/PatchWarden-Control-Tray.cmd +11 -0
- package/PatchWarden-Control.cmd +6 -0
- package/PatchWarden-Desktop.cmd +5 -0
- package/PatchWarden.cmd +1 -1
- package/README.en.md +106 -18
- package/README.md +30 -19
- package/Restart-PatchWarden-Control.cmd +6 -0
- package/Stop-PatchWarden.cmd +11 -0
- package/dist/controlCenter.d.ts +14 -0
- package/dist/controlCenter.js +2002 -0
- package/dist/doctor.js +4 -4
- package/dist/logging.d.ts +52 -0
- package/dist/logging.js +123 -0
- package/dist/runner/changeCapture.d.ts +41 -0
- package/dist/runner/changeCapture.js +171 -1
- package/dist/runner/runTask.js +204 -24
- package/dist/smoke-test.js +8 -8
- package/dist/test/unit/android-doctor.test.d.ts +1 -0
- package/dist/test/unit/android-doctor.test.js +118 -0
- package/dist/test/unit/chinese-path.test.d.ts +1 -0
- package/dist/test/unit/chinese-path.test.js +91 -0
- package/dist/test/unit/command-guard.test.d.ts +1 -0
- package/dist/test/unit/command-guard.test.js +160 -0
- package/dist/test/unit/direct-guards.test.d.ts +1 -0
- package/dist/test/unit/direct-guards.test.js +213 -0
- package/dist/test/unit/logging.test.d.ts +1 -0
- package/dist/test/unit/logging.test.js +275 -0
- package/dist/test/unit/path-guard.test.d.ts +1 -0
- package/dist/test/unit/path-guard.test.js +109 -0
- package/dist/test/unit/safe-status.test.d.ts +1 -0
- package/dist/test/unit/safe-status.test.js +165 -0
- package/dist/test/unit/sensitive-guard.test.d.ts +1 -0
- package/dist/test/unit/sensitive-guard.test.js +104 -0
- package/dist/test/unit/sync-file.test.d.ts +1 -0
- package/dist/test/unit/sync-file.test.js +154 -0
- package/dist/test/unit/watcher-status.test.d.ts +1 -0
- package/dist/test/unit/watcher-status.test.js +169 -0
- package/dist/tools/androidDoctor.d.ts +38 -0
- package/dist/tools/androidDoctor.js +391 -0
- package/dist/tools/auditTask.js +11 -5
- package/dist/tools/getTaskSummary.d.ts +3 -0
- package/dist/tools/getTaskSummary.js +15 -1
- package/dist/tools/healthCheck.d.ts +5 -0
- package/dist/tools/healthCheck.js +21 -0
- package/dist/tools/registry.js +53 -0
- package/dist/tools/safeStatus.d.ts +19 -0
- package/dist/tools/safeStatus.js +72 -0
- package/dist/tools/syncFile.d.ts +18 -0
- package/dist/tools/syncFile.js +65 -0
- package/dist/tools/taskOutputs.d.ts +2 -2
- package/dist/tools/toolCatalog.d.ts +2 -2
- package/dist/tools/toolCatalog.js +2 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +1 -0
- package/dist/watcherStatus.js +96 -4
- package/docs/control-center/README.md +33 -0
- package/docs/control-center/control-center-daily-driver.md +211 -0
- package/docs/control-center/control-center-mvp.md +205 -0
- package/docs/control-center/control-center-phase2.md +159 -0
- package/docs/demo.md +3 -0
- package/docs/performance-notes.md +55 -0
- package/docs/release-v0.6.1.md +75 -0
- package/docs/release-v0.6.4.md +45 -0
- package/examples/openai-tunnel/README.md +5 -5
- package/examples/openai-tunnel/tunnel-client.example.yaml +3 -3
- package/package.json +23 -14
- package/scripts/README.md +47 -0
- package/scripts/{brand-check.js → checks/brand-check.js} +2 -2
- package/scripts/checks/control-center-smoke.js +1098 -0
- package/scripts/{control-smoke.js → checks/control-smoke.js} +17 -3
- package/scripts/{doctor-smoke.js → checks/doctor-smoke.js} +1 -1
- package/scripts/{http-mcp-smoke.js → checks/http-mcp-smoke.js} +12 -4
- package/scripts/{lifecycle-smoke.js → checks/lifecycle-smoke.js} +347 -13
- package/scripts/{mcp-manifest-check.js → checks/mcp-manifest-check.js} +32 -9
- package/scripts/{mcp-smoke.js → checks/mcp-smoke.js} +14 -9
- package/scripts/{package-manifest-check.js → checks/package-manifest-check.js} +2 -1
- package/scripts/{tunnel-supervisor-smoke.js → checks/tunnel-supervisor-smoke.js} +2 -2
- package/scripts/checks/unit-tests.js +36 -0
- package/scripts/{watcher-supervisor-smoke.js → checks/watcher-supervisor-smoke.js} +11 -5
- package/scripts/control/control-center-tray.ps1 +281 -0
- package/scripts/{get-patchwarden-health.ps1 → control/get-patchwarden-health.ps1} +3 -3
- package/scripts/{manage-patchwarden.ps1 → control/manage-patchwarden.ps1} +30 -4
- package/scripts/control/restart-control-center.ps1 +173 -0
- package/scripts/{restart-patchwarden.ps1 → control/restart-patchwarden.ps1} +1 -1
- package/scripts/control/start-control-center.ps1 +263 -0
- package/scripts/{start-patchwarden-tunnel.ps1 → control/start-patchwarden-tunnel.ps1} +48 -6
- package/scripts/control/stop-patchwarden.ps1 +114 -0
- package/scripts/launchers/Check-PatchWarden-Health.cmd +1 -1
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +1 -1
- package/scripts/launchers/Restart-PatchWarden.cmd +1 -1
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +1 -1
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +1 -1
- package/scripts/{patchwarden-mcp-direct.cmd → mcp/patchwarden-mcp-direct.cmd} +1 -1
- package/scripts/{patchwarden-mcp-stdio.cmd → mcp/patchwarden-mcp-stdio.cmd} +1 -1
- package/scripts/release/pack-clean.js +305 -0
- package/src/controlCenter.ts +2152 -0
- package/src/doctor.ts +5 -5
- package/src/logging.ts +152 -0
- package/src/runner/changeCapture.ts +212 -1
- package/src/runner/runTask.ts +220 -22
- package/src/smoke-test.ts +5 -5
- package/src/test/unit/android-doctor.test.ts +158 -0
- package/src/test/unit/chinese-path.test.ts +106 -0
- package/src/test/unit/command-guard.test.ts +221 -0
- package/src/test/unit/direct-guards.test.ts +297 -0
- package/src/test/unit/logging.test.ts +325 -0
- package/src/test/unit/path-guard.test.ts +150 -0
- package/src/test/unit/safe-status.test.ts +187 -0
- package/src/test/unit/sensitive-guard.test.ts +124 -0
- package/src/test/unit/sync-file.test.ts +231 -0
- package/src/test/unit/watcher-status.test.ts +190 -0
- package/src/tools/androidDoctor.ts +424 -0
- package/src/tools/auditTask.ts +11 -5
- package/src/tools/getTaskSummary.ts +22 -1
- package/src/tools/healthCheck.ts +22 -0
- package/src/tools/registry.ts +63 -0
- package/src/tools/safeStatus.ts +96 -0
- package/src/tools/syncFile.ts +122 -0
- package/src/tools/toolCatalog.ts +2 -0
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +101 -4
- package/ui/colors_and_type.css +141 -0
- package/ui/pages/audit.html +743 -0
- package/ui/pages/dashboard.html +1154 -0
- package/ui/pages/direct-sessions.html +652 -0
- package/ui/pages/logs.html +502 -0
- package/ui/pages/task-detail.html +1229 -0
- package/ui/pages/tasks.html +702 -0
- package/ui/pages/workspace.html +947 -0
- package/ui/partials/project-shell.html +362 -0
- package/ui/vendor/lucide.js +12 -0
- package/ui/vendor/tailwindcss-browser.js +947 -0
- package/scripts/pack-clean.js +0 -141
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const scriptDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
8
|
+
const root = resolve(scriptDir, "..", "..");
|
|
9
|
+
const unitDir = resolve(root, "dist", "test", "unit");
|
|
10
|
+
|
|
11
|
+
if (!existsSync(unitDir)) {
|
|
12
|
+
console.error(`[unit-tests] Missing compiled test directory: ${unitDir}`);
|
|
13
|
+
console.error("[unit-tests] Run npm run build before npm run test:unit.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const testFiles = readdirSync(unitDir)
|
|
18
|
+
.filter((name) => name.endsWith(".test.js"))
|
|
19
|
+
.sort()
|
|
20
|
+
.map((name) => resolve(unitDir, name));
|
|
21
|
+
|
|
22
|
+
if (testFiles.length === 0) {
|
|
23
|
+
console.error(`[unit-tests] No compiled unit tests found in ${unitDir}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = spawnSync(process.execPath, ["--test", ...testFiles], {
|
|
28
|
+
stdio: "inherit",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (result.error) {
|
|
32
|
+
console.error(`[unit-tests] Failed to run unit tests: ${result.error.message}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
process.exit(typeof result.status === "number" ? result.status : 1);
|
|
@@ -19,7 +19,7 @@ if (process.platform !== "win32") {
|
|
|
19
19
|
process.exit(0);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const root = resolve(fileURLToPath(new URL("
|
|
22
|
+
const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
|
|
23
23
|
const temp = mkdtempSync(join(tmpdir(), "patchwarden-watcher-supervisor-"));
|
|
24
24
|
const children = [];
|
|
25
25
|
|
|
@@ -87,13 +87,19 @@ async function runExternalScenario() {
|
|
|
87
87
|
function createFixture(name) {
|
|
88
88
|
const project = join(temp, name, "patchwarden-fixture");
|
|
89
89
|
const scripts = join(project, "scripts");
|
|
90
|
+
const controlScripts = join(scripts, "control");
|
|
91
|
+
const mcpScripts = join(scripts, "mcp");
|
|
92
|
+
const checkScripts = join(scripts, "checks");
|
|
90
93
|
const runner = join(project, "dist", "runner");
|
|
91
94
|
const workspace = join(project, "workspace");
|
|
92
95
|
mkdirSync(scripts, { recursive: true });
|
|
96
|
+
mkdirSync(controlScripts, { recursive: true });
|
|
97
|
+
mkdirSync(mcpScripts, { recursive: true });
|
|
98
|
+
mkdirSync(checkScripts, { recursive: true });
|
|
93
99
|
mkdirSync(runner, { recursive: true });
|
|
94
100
|
mkdirSync(workspace, { recursive: true });
|
|
95
|
-
cpSync(join(root, "scripts", "start-patchwarden-tunnel.ps1"), join(
|
|
96
|
-
writeFileSync(join(
|
|
101
|
+
cpSync(join(root, "scripts", "control", "start-patchwarden-tunnel.ps1"), join(controlScripts, "start-patchwarden-tunnel.ps1"));
|
|
102
|
+
writeFileSync(join(mcpScripts, "patchwarden-mcp-stdio.cmd"), "@echo off\r\nexit /b 0\r\n", "utf-8");
|
|
97
103
|
const manifestFixture = JSON.stringify({
|
|
98
104
|
ok: true,
|
|
99
105
|
server_version: "0.6.0",
|
|
@@ -103,7 +109,7 @@ function createFixture(name) {
|
|
|
103
109
|
tool_names: [],
|
|
104
110
|
tool_manifest_sha256: "a".repeat(64),
|
|
105
111
|
});
|
|
106
|
-
writeFileSync(join(
|
|
112
|
+
writeFileSync(join(checkScripts, "mcp-manifest-check.js"), `process.stdout.write(${JSON.stringify(manifestFixture)});\n`, "utf-8");
|
|
107
113
|
writeFileSync(join(project, "dist", "index.js"), "", "utf-8");
|
|
108
114
|
const watcherAttemptPath = join(project, "watcher-attempt.txt");
|
|
109
115
|
const watcherPath = join(runner, "watch.js");
|
|
@@ -140,7 +146,7 @@ function createFixture(name) {
|
|
|
140
146
|
|
|
141
147
|
function runLauncher(fixture, mode, maxRestarts, lifetimeMs) {
|
|
142
148
|
return spawnSync("powershell.exe", [
|
|
143
|
-
"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(fixture.project, "scripts", "start-patchwarden-tunnel.ps1"),
|
|
149
|
+
"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(fixture.project, "scripts", "control", "start-patchwarden-tunnel.ps1"),
|
|
144
150
|
"-TunnelId", "tunnel_watcher_fixture",
|
|
145
151
|
"-TunnelClientExe", fixture.mockCmd,
|
|
146
152
|
"-ProxyUrl", "http://127.0.0.1:1",
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Lightweight PatchWarden Control Center system tray entry.
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Builds a Windows NotifyIcon (no Electron, no extra dependencies) whose menu
|
|
7
|
+
drives the Control Center via its HTTP API with the control token. If the
|
|
8
|
+
Control Center is not running when the tray starts, it is launched in the
|
|
9
|
+
background first.
|
|
10
|
+
|
|
11
|
+
Menu:
|
|
12
|
+
- Open Dashboard (opens the dashboard in the default browser)
|
|
13
|
+
- Status (shows a compact Core/Direct/Watcher summary)
|
|
14
|
+
- Start All (POST /api/start-all)
|
|
15
|
+
- Stop All (POST /api/stop-all)
|
|
16
|
+
- Restart All (POST /api/restart-all)
|
|
17
|
+
- Open Logs (POST /api/open-logs-folder)
|
|
18
|
+
- Quit Tray (disposes the tray and exits)
|
|
19
|
+
#>
|
|
20
|
+
param(
|
|
21
|
+
[int]$Port = 8090,
|
|
22
|
+
[switch]$NoStartupBalloon
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
$ErrorActionPreference = "Stop"
|
|
26
|
+
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
|
27
|
+
$BaseUrl = "http://127.0.0.1:$Port/"
|
|
28
|
+
$RuntimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\control-center"
|
|
29
|
+
$StatusFile = Join-Path $RuntimeDirectory "control-center-status.json"
|
|
30
|
+
$MutexName = "Local\PatchWarden-Control-Tray-" + ([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($ProjectRoot)) -replace "[^A-Za-z0-9]", "").Substring(0, 16)
|
|
31
|
+
|
|
32
|
+
# ── Load WinForms + Drawing ──────────────────────────────────────
|
|
33
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
34
|
+
Add-Type -AssemblyName System.Drawing
|
|
35
|
+
|
|
36
|
+
$createdMutex = $false
|
|
37
|
+
$trayMutex = New-Object System.Threading.Mutex($true, $MutexName, [ref]$createdMutex)
|
|
38
|
+
if (-not $createdMutex) {
|
|
39
|
+
$trayMutex.Dispose()
|
|
40
|
+
exit 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ── Helpers ──────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
function Invoke-ControlApi {
|
|
46
|
+
param([Parameter(Mandatory = $true)][string]$Path, [string]$Method = "POST")
|
|
47
|
+
$url = $BaseUrl.TrimEnd("/") + $Path
|
|
48
|
+
# Fetch the control token (in-memory, per server run).
|
|
49
|
+
$token = $null
|
|
50
|
+
try {
|
|
51
|
+
$tokenResp = Invoke-WebRequest -Uri ($BaseUrl.TrimEnd("/") + "/control-token.json") -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop
|
|
52
|
+
$token = ($tokenResp.Content | ConvertFrom-Json).token
|
|
53
|
+
} catch {
|
|
54
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
55
|
+
"Could not reach the Control Center at $BaseUrl.`n`nError: $($_.Exception.Message)",
|
|
56
|
+
"PatchWarden Tray",
|
|
57
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
58
|
+
[System.Windows.Forms.MessageBoxIcon]::Warning
|
|
59
|
+
) | Out-Null
|
|
60
|
+
return $null
|
|
61
|
+
}
|
|
62
|
+
$headers = @{ "X-PatchWarden-Control-Token" = $token }
|
|
63
|
+
try {
|
|
64
|
+
$resp = Invoke-WebRequest -Uri $url -Method $Method -Headers $headers -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop
|
|
65
|
+
return $resp.Content | ConvertFrom-Json
|
|
66
|
+
} catch {
|
|
67
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
68
|
+
"API call failed: $Path`n`nError: $($_.Exception.Message)",
|
|
69
|
+
"PatchWarden Tray",
|
|
70
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
71
|
+
[System.Windows.Forms.MessageBoxIcon]::Warning
|
|
72
|
+
) | Out-Null
|
|
73
|
+
return $null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function New-PatchWardenTrayIcon {
|
|
78
|
+
$bitmap = New-Object System.Drawing.Bitmap 32, 32
|
|
79
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
80
|
+
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
|
81
|
+
$graphics.Clear([System.Drawing.Color]::Transparent)
|
|
82
|
+
|
|
83
|
+
$background = New-Object System.Drawing.SolidBrush -ArgumentList ([System.Drawing.Color]::FromArgb(255, 10, 14, 20))
|
|
84
|
+
$accent = New-Object System.Drawing.SolidBrush -ArgumentList ([System.Drawing.Color]::FromArgb(255, 45, 212, 168))
|
|
85
|
+
$darkPen = New-Object System.Drawing.Pen -ArgumentList ([System.Drawing.Color]::FromArgb(255, 5, 9, 14)), 1.5
|
|
86
|
+
|
|
87
|
+
$graphics.FillEllipse($background, 1, 1, 30, 30)
|
|
88
|
+
$points = [System.Drawing.Point[]]@(
|
|
89
|
+
[System.Drawing.Point]::new(16, 5),
|
|
90
|
+
[System.Drawing.Point]::new(25, 9),
|
|
91
|
+
[System.Drawing.Point]::new(24, 17),
|
|
92
|
+
[System.Drawing.Point]::new(16, 27),
|
|
93
|
+
[System.Drawing.Point]::new(8, 17),
|
|
94
|
+
[System.Drawing.Point]::new(7, 9)
|
|
95
|
+
)
|
|
96
|
+
$graphics.FillPolygon($accent, $points)
|
|
97
|
+
$graphics.DrawPolygon($darkPen, $points)
|
|
98
|
+
$graphics.DrawLine($darkPen, 16, 8, 16, 23)
|
|
99
|
+
$graphics.DrawLine($darkPen, 11, 15, 21, 15)
|
|
100
|
+
|
|
101
|
+
$handle = $bitmap.GetHicon()
|
|
102
|
+
try {
|
|
103
|
+
return [System.Drawing.Icon]::FromHandle($handle)
|
|
104
|
+
} finally {
|
|
105
|
+
$graphics.Dispose()
|
|
106
|
+
$background.Dispose()
|
|
107
|
+
$accent.Dispose()
|
|
108
|
+
$darkPen.Dispose()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function Show-TrayBalloon {
|
|
113
|
+
param([string]$Title, [string]$Text, [System.Windows.Forms.ToolTipIcon]$Icon = [System.Windows.Forms.ToolTipIcon]::Info)
|
|
114
|
+
if ($null -eq $script:notifyIcon) { return }
|
|
115
|
+
$script:notifyIcon.BalloonTipTitle = $Title
|
|
116
|
+
$script:notifyIcon.BalloonTipText = $Text
|
|
117
|
+
$script:notifyIcon.BalloonTipIcon = $Icon
|
|
118
|
+
$script:notifyIcon.ShowBalloonTip(2200)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function Invoke-TrayControlAction {
|
|
122
|
+
param([string]$Path, [string]$SuccessText)
|
|
123
|
+
$result = Invoke-ControlApi -Path $Path
|
|
124
|
+
if ($null -eq $result) { return }
|
|
125
|
+
if ($result.ok -eq $false) {
|
|
126
|
+
Show-TrayBalloon -Title "PatchWarden needs attention" -Text "Open Dashboard for details." -Icon ([System.Windows.Forms.ToolTipIcon]::Warning)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
Show-TrayBalloon -Title "PatchWarden" -Text $SuccessText
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function Test-ControlCenterRunning {
|
|
133
|
+
# Probe the status endpoint to confirm the server is live and is ours.
|
|
134
|
+
try {
|
|
135
|
+
$resp = Invoke-WebRequest -Uri ($BaseUrl.TrimEnd("/") + "/api/control-center-status") -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
|
|
136
|
+
if ($resp.StatusCode -eq 200) {
|
|
137
|
+
$body = $resp.Content | ConvertFrom-Json
|
|
138
|
+
return [bool]$body.running
|
|
139
|
+
}
|
|
140
|
+
} catch {}
|
|
141
|
+
return $false
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function Ensure-ControlCenterRunning {
|
|
145
|
+
if (Test-ControlCenterRunning) { return $true }
|
|
146
|
+
Write-Host "[tray] Preparing PatchWarden Control Center..."
|
|
147
|
+
$startScript = Join-Path $PSScriptRoot "start-control-center.ps1"
|
|
148
|
+
if (-not (Test-Path -LiteralPath $startScript)) {
|
|
149
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
150
|
+
"start-control-center.ps1 not found at:`n$startScript",
|
|
151
|
+
"PatchWarden Tray",
|
|
152
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
153
|
+
[System.Windows.Forms.MessageBoxIcon]::Error
|
|
154
|
+
) | Out-Null
|
|
155
|
+
return $false
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
Start-Process -FilePath "powershell.exe" `
|
|
159
|
+
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $startScript, "-NoBrowser") `
|
|
160
|
+
-WindowStyle Hidden -Wait -ErrorAction Stop
|
|
161
|
+
} catch {
|
|
162
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
163
|
+
"Failed to start the Control Center:`n$($_.Exception.Message)",
|
|
164
|
+
"PatchWarden Tray",
|
|
165
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
166
|
+
[System.Windows.Forms.MessageBoxIcon]::Error
|
|
167
|
+
) | Out-Null
|
|
168
|
+
return $false
|
|
169
|
+
}
|
|
170
|
+
# Wait up to 20s for readiness.
|
|
171
|
+
for ($i = 0; $i -lt 20; $i++) {
|
|
172
|
+
Start-Sleep -Seconds 1
|
|
173
|
+
if (Test-ControlCenterRunning) { return $true }
|
|
174
|
+
}
|
|
175
|
+
return $false
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function Show-CompactStatus {
|
|
179
|
+
$data = Invoke-ControlApi -Path "/api/status" -Method "GET"
|
|
180
|
+
if ($null -eq $data) { return }
|
|
181
|
+
|
|
182
|
+
$core = if ($data.core.available) { "available" } else { "stopped" }
|
|
183
|
+
$direct = if ($data.direct.available) { "available" } else { "stopped" }
|
|
184
|
+
$watcher = if ($data.watcher.status) { [string]$data.watcher.status } else { "unknown" }
|
|
185
|
+
$workspace = if ($data.workspace_root) { [string]$data.workspace_root } else { "not configured" }
|
|
186
|
+
|
|
187
|
+
$message = @(
|
|
188
|
+
"Core: $core"
|
|
189
|
+
"Direct: $direct"
|
|
190
|
+
"Watcher: $watcher"
|
|
191
|
+
""
|
|
192
|
+
"Workspace:"
|
|
193
|
+
$workspace
|
|
194
|
+
""
|
|
195
|
+
"Open Dashboard for tasks, audit logs, Direct sessions, and setup details."
|
|
196
|
+
) -join "`n"
|
|
197
|
+
|
|
198
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
199
|
+
$message,
|
|
200
|
+
"PatchWarden Status",
|
|
201
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
202
|
+
[System.Windows.Forms.MessageBoxIcon]::Information
|
|
203
|
+
) | Out-Null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
# ── Ensure the control center is up before showing the tray ──────
|
|
207
|
+
[void](Ensure-ControlCenterRunning)
|
|
208
|
+
|
|
209
|
+
# ── Build the tray icon ──────────────────────────────────────────
|
|
210
|
+
$script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
211
|
+
$notifyIcon = $script:notifyIcon
|
|
212
|
+
try {
|
|
213
|
+
$notifyIcon.Icon = New-PatchWardenTrayIcon
|
|
214
|
+
} catch {
|
|
215
|
+
$notifyIcon.Icon = [System.Drawing.SystemIcons]::Shield
|
|
216
|
+
}
|
|
217
|
+
$notifyIcon.Text = "PatchWarden: local control"
|
|
218
|
+
$notifyIcon.Visible = $true
|
|
219
|
+
|
|
220
|
+
# Double-click opens the dashboard.
|
|
221
|
+
$notifyIcon.Add_DoubleClick({
|
|
222
|
+
Start-Process -FilePath $BaseUrl
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
# ── Context menu ─────────────────────────────────────────────────
|
|
226
|
+
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
227
|
+
|
|
228
|
+
[void]$menu.Items.Add("Open Dashboard", $null, {
|
|
229
|
+
Start-Process -FilePath $BaseUrl
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
[void]$menu.Items.Add("Status", $null, {
|
|
233
|
+
Show-CompactStatus
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
[void]$menu.Items.Add("-") # separator
|
|
237
|
+
|
|
238
|
+
[void]$menu.Items.Add("Start All", $null, {
|
|
239
|
+
Invoke-TrayControlAction -Path "/api/start-all" -SuccessText "Core and Direct are starting in the background."
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
[void]$menu.Items.Add("Stop All", $null, {
|
|
243
|
+
Invoke-TrayControlAction -Path "/api/stop-all" -SuccessText "Core and Direct were asked to stop. Tray and dashboard stay available."
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
[void]$menu.Items.Add("Restart All", $null, {
|
|
247
|
+
Invoke-TrayControlAction -Path "/api/restart-all" -SuccessText "Core and Direct are restarting in the background."
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
[void]$menu.Items.Add("-") # separator
|
|
251
|
+
|
|
252
|
+
[void]$menu.Items.Add("Open Logs", $null, {
|
|
253
|
+
Invoke-TrayControlAction -Path "/api/open-logs-folder" -SuccessText "Opening PatchWarden runtime logs."
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
[void]$menu.Items.Add("-") # separator
|
|
257
|
+
|
|
258
|
+
[void]$menu.Items.Add("Quit Tray", $null, {
|
|
259
|
+
$notifyIcon.Visible = $false
|
|
260
|
+
[System.Windows.Forms.Application]::Exit()
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
$notifyIcon.ContextMenuStrip = $menu
|
|
264
|
+
|
|
265
|
+
# Show a brief balloon on launch.
|
|
266
|
+
if (-not $NoStartupBalloon) {
|
|
267
|
+
Show-TrayBalloon -Title "PatchWarden is ready" -Text "Right-click for quick controls. Open Dashboard for the full console."
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Write-Host "[tray] PatchWarden Control Center tray is active. Right-click the icon for actions."
|
|
271
|
+
|
|
272
|
+
# Run the application loop. Without this the script exits immediately and the
|
|
273
|
+
# tray disappears. The loop exits when Application::Exit() is called from the
|
|
274
|
+
# Exit menu item.
|
|
275
|
+
[System.Windows.Forms.Application]::Run()
|
|
276
|
+
|
|
277
|
+
# Cleanup on exit.
|
|
278
|
+
$notifyIcon.Visible = $false
|
|
279
|
+
$notifyIcon.Dispose()
|
|
280
|
+
if ($trayMutex) { $trayMutex.ReleaseMutex(); $trayMutex.Dispose() }
|
|
281
|
+
Write-Host "[tray] Exited."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
$ErrorActionPreference = "Stop"
|
|
2
2
|
|
|
3
|
-
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
|
3
|
+
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
|
4
4
|
$ConfigPath = Join-Path $ProjectRoot "patchwarden.config.json"
|
|
5
5
|
$RuntimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\runtime"
|
|
6
6
|
$HealthUrlFile = Join-Path $RuntimeDirectory "tunnel-health-url.txt"
|
|
@@ -15,7 +15,7 @@ $packageVersion = [string](Get-Content -LiteralPath (Join-Path $ProjectRoot "pac
|
|
|
15
15
|
|
|
16
16
|
$configuredTunnelManifest = $null
|
|
17
17
|
try {
|
|
18
|
-
$manifestJson = node (Join-Path $ProjectRoot "scripts\mcp-manifest-check.js") 2>$null
|
|
18
|
+
$manifestJson = node (Join-Path $ProjectRoot "scripts\checks\mcp-manifest-check.js") 2>$null
|
|
19
19
|
if ($LASTEXITCODE -eq 0 -and $manifestJson) {
|
|
20
20
|
$configuredTunnelManifest = $manifestJson | ConvertFrom-Json
|
|
21
21
|
}
|
|
@@ -70,7 +70,7 @@ if (Test-Path -LiteralPath $HealthUrlFile) {
|
|
|
70
70
|
|
|
71
71
|
$tunnelProcesses = @($safeProcesses | Where-Object { $_.name -eq "tunnel-client.exe" })
|
|
72
72
|
$mcpChildProcesses = @($safeProcesses | Where-Object {
|
|
73
|
-
$_.command_line -match '(?i)patchwarden-mcp-stdio\.cmd|patchwarden\\dist\\index\.js|patchwarden/scripts
|
|
73
|
+
$_.command_line -match '(?i)patchwarden-mcp-stdio\.cmd|patchwarden\\dist\\index\.js|patchwarden/scripts/mcp/../../dist/index.js'
|
|
74
74
|
})
|
|
75
75
|
$deploymentConsistent = [bool](
|
|
76
76
|
$configuredTunnelManifest -and
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[CmdletBinding()]
|
|
1
|
+
[CmdletBinding()]
|
|
2
2
|
param(
|
|
3
3
|
[Parameter(Position = 0)]
|
|
4
4
|
[ValidateSet("menu", "start", "stop", "restart", "status", "health", "reset-key", "kill")]
|
|
@@ -11,13 +11,14 @@ param(
|
|
|
11
11
|
[switch]$SkipBuild,
|
|
12
12
|
[switch]$WhatIf,
|
|
13
13
|
[switch]$Json,
|
|
14
|
+
[switch]$Background,
|
|
14
15
|
[int]$KillTimeoutSeconds = 10
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
$ErrorActionPreference = "Stop"
|
|
18
19
|
$ProgressPreference = "SilentlyContinue"
|
|
19
|
-
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
|
20
|
-
$LauncherDirectory = Join-Path $
|
|
20
|
+
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
|
21
|
+
$LauncherDirectory = Join-Path $ProjectRoot "scripts\launchers"
|
|
21
22
|
$LocalLauncherDirectory = Join-Path $ProjectRoot ".local\launchers"
|
|
22
23
|
$PatchWardenRuntimeRoot = Join-Path $env:LOCALAPPDATA "patchwarden"
|
|
23
24
|
|
|
@@ -518,7 +519,32 @@ function Start-Mode {
|
|
|
518
519
|
}
|
|
519
520
|
Assert-NoUnsafePortConflicts -Definitions @($Definition)
|
|
520
521
|
|
|
521
|
-
|
|
522
|
+
if ($Background) {
|
|
523
|
+
$launcherScript = Join-Path $PSScriptRoot "start-patchwarden-tunnel.ps1"
|
|
524
|
+
if (-not (Test-Path -LiteralPath $launcherScript)) { throw "Launcher script not found: $launcherScript" }
|
|
525
|
+
$healthListenAddr = ([Uri]$Definition.HealthBaseUrl).Authority
|
|
526
|
+
$arguments = @(
|
|
527
|
+
"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $launcherScript,
|
|
528
|
+
"-ToolProfile", $Definition.ToolProfile,
|
|
529
|
+
"-Profile", $Definition.Profile,
|
|
530
|
+
"-HealthListenAddr", $healthListenAddr,
|
|
531
|
+
"-NoTunnelWebUi"
|
|
532
|
+
)
|
|
533
|
+
if (-not $Definition.HasWatcher) { $arguments += "-SkipWatcher" }
|
|
534
|
+
if ($WhatIf) {
|
|
535
|
+
Write-Host "[start:$($Definition.Key)] WHAT-IF: start hidden supervisor for $($Definition.Profile)" -ForegroundColor Magenta
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
$process = Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -WorkingDirectory $ProjectRoot -PassThru -WindowStyle Hidden
|
|
539
|
+
Write-Host "[start:$($Definition.Key)] Started hidden supervisor PID $($process.Id). Logs: $($Definition.RuntimeDirectory)" -ForegroundColor Green
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
# Prefer the project's built-in launcher (scripts\launchers\*.cmd) over
|
|
544
|
+
# .local\launchers\*.local.cmd. A .local launcher, if present, MUST call
|
|
545
|
+
# the project root scripts\launchers\*.cmd via relative path (..\..\scripts\launchers\*),
|
|
546
|
+
# NOT assume its own directory contains the target script.
|
|
547
|
+
$launcher = if (Test-Path -LiteralPath $Definition.GenericLauncher) { $Definition.GenericLauncher } else { $Definition.LocalLauncher }
|
|
522
548
|
if (-not (Test-Path -LiteralPath $launcher)) { throw "Launcher not found: $launcher" }
|
|
523
549
|
$launcherName = Split-Path -Leaf $launcher
|
|
524
550
|
if ($WhatIf) {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
param(
|
|
3
|
+
[switch]$NoBrowser,
|
|
4
|
+
[switch]$NoBuild
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$ErrorActionPreference = "Stop"
|
|
8
|
+
|
|
9
|
+
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
|
10
|
+
$ControlCenterEntry = Join-Path $ProjectRoot "dist\controlCenter.js"
|
|
11
|
+
$ControlCenterEntryFull = [System.IO.Path]::GetFullPath($ControlCenterEntry)
|
|
12
|
+
$ProjectRootFull = [System.IO.Path]::GetFullPath($ProjectRoot).TrimEnd("\")
|
|
13
|
+
$ListenPort = 8090
|
|
14
|
+
$ListenUrl = "http://127.0.0.1:$ListenPort/"
|
|
15
|
+
$RuntimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\control-center"
|
|
16
|
+
$StdoutLog = Join-Path $RuntimeDirectory "control-center.stdout.log"
|
|
17
|
+
$StderrLog = Join-Path $RuntimeDirectory "control-center.stderr.log"
|
|
18
|
+
$MaxWaitSeconds = 15
|
|
19
|
+
|
|
20
|
+
Set-Location -LiteralPath $ProjectRoot
|
|
21
|
+
|
|
22
|
+
function Test-ControlCenterProcess {
|
|
23
|
+
param([Parameter(Mandatory = $true)]$ProcessInfo)
|
|
24
|
+
|
|
25
|
+
$commandLine = [string]$ProcessInfo.CommandLine
|
|
26
|
+
if (-not $commandLine) { return $false }
|
|
27
|
+
|
|
28
|
+
$normalizedCommand = $commandLine.Replace("/", "\").ToLowerInvariant()
|
|
29
|
+
$normalizedEntry = $ControlCenterEntryFull.ToLowerInvariant()
|
|
30
|
+
$normalizedRoot = $ProjectRootFull.ToLowerInvariant()
|
|
31
|
+
|
|
32
|
+
return $normalizedCommand.Contains($normalizedEntry) -or (
|
|
33
|
+
$normalizedCommand.Contains("dist\controlcenter.js") -and
|
|
34
|
+
$normalizedCommand.Contains($normalizedRoot)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function Get-ControlCenterProcesses {
|
|
39
|
+
Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" |
|
|
40
|
+
Where-Object { Test-ControlCenterProcess $_ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function Assert-PortIsNotForeign {
|
|
44
|
+
try {
|
|
45
|
+
$listeners = Get-NetTCPConnection -LocalPort $ListenPort -State Listen -ErrorAction Stop
|
|
46
|
+
} catch {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
foreach ($listener in $listeners) {
|
|
51
|
+
$owner = Get-CimInstance Win32_Process -Filter "ProcessId = $($listener.OwningProcess)" -ErrorAction SilentlyContinue
|
|
52
|
+
if (-not $owner) { continue }
|
|
53
|
+
if (-not (Test-ControlCenterProcess $owner)) {
|
|
54
|
+
Write-Host "[error] Port $ListenPort is used by a non-PatchWarden Control Center process." -ForegroundColor Red
|
|
55
|
+
Write-Host "[error] PID $($owner.ProcessId): $($owner.CommandLine)" -ForegroundColor DarkRed
|
|
56
|
+
Write-Host "[error] Close that process manually, then rerun this script." -ForegroundColor Yellow
|
|
57
|
+
exit 1
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Stop-ControlCenterProcesses {
|
|
63
|
+
$processes = @(Get-ControlCenterProcesses)
|
|
64
|
+
if ($processes.Count -eq 0) {
|
|
65
|
+
Write-Host "[stop] No existing PatchWarden Control Center process found." -ForegroundColor DarkGray
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
foreach ($proc in $processes) {
|
|
70
|
+
Write-Host "[stop] Stopping Control Center PID $($proc.ProcessId)." -ForegroundColor Yellow
|
|
71
|
+
Stop-Process -Id $proc.ProcessId -ErrorAction SilentlyContinue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Start-Sleep -Milliseconds 800
|
|
75
|
+
|
|
76
|
+
foreach ($proc in $processes) {
|
|
77
|
+
$stillRunning = Get-Process -Id $proc.ProcessId -ErrorAction SilentlyContinue
|
|
78
|
+
if ($stillRunning) {
|
|
79
|
+
Write-Host "[stop] PID $($proc.ProcessId) did not exit cleanly; forcing stop." -ForegroundColor Yellow
|
|
80
|
+
Stop-Process -Id $proc.ProcessId -Force -ErrorAction SilentlyContinue
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function Invoke-BuildIfNeeded {
|
|
86
|
+
if ($NoBuild) {
|
|
87
|
+
Write-Host "[build] Skipped by -NoBuild." -ForegroundColor DarkGray
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Write-Host "[build] Running npm.cmd run build..." -ForegroundColor Cyan
|
|
92
|
+
npm.cmd run build
|
|
93
|
+
if ($LASTEXITCODE -ne 0) {
|
|
94
|
+
Write-Host "[error] npm.cmd run build exited with code $LASTEXITCODE." -ForegroundColor Red
|
|
95
|
+
exit $LASTEXITCODE
|
|
96
|
+
}
|
|
97
|
+
if (-not (Test-Path -LiteralPath $ControlCenterEntryFull)) {
|
|
98
|
+
Write-Host "[error] Build completed but dist\controlCenter.js is missing." -ForegroundColor Red
|
|
99
|
+
exit 1
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Start-ControlCenter {
|
|
104
|
+
$nodeCommand = Get-Command node.exe -ErrorAction SilentlyContinue
|
|
105
|
+
if (-not $nodeCommand) {
|
|
106
|
+
Write-Host "[error] node.exe was not found on PATH." -ForegroundColor Red
|
|
107
|
+
exit 1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
New-Item -ItemType Directory -Force -Path $RuntimeDirectory | Out-Null
|
|
111
|
+
Remove-Item -LiteralPath $StdoutLog, $StderrLog -Force -ErrorAction SilentlyContinue
|
|
112
|
+
|
|
113
|
+
Write-Host "[run] Starting PatchWarden Control Center on $ListenUrl" -ForegroundColor Cyan
|
|
114
|
+
$process = Start-Process -FilePath $nodeCommand.Source `
|
|
115
|
+
-ArgumentList @($ControlCenterEntryFull) `
|
|
116
|
+
-WorkingDirectory $ProjectRoot `
|
|
117
|
+
-RedirectStandardOutput $StdoutLog `
|
|
118
|
+
-RedirectStandardError $StderrLog `
|
|
119
|
+
-PassThru -WindowStyle Hidden
|
|
120
|
+
|
|
121
|
+
Start-Sleep -Milliseconds 500
|
|
122
|
+
if ($process.HasExited) {
|
|
123
|
+
Write-Host "[error] Control Center exited immediately with code $($process.ExitCode)." -ForegroundColor Red
|
|
124
|
+
if (Test-Path -LiteralPath $StderrLog) {
|
|
125
|
+
Get-Content -LiteralPath $StderrLog -Tail 20 -ErrorAction SilentlyContinue
|
|
126
|
+
}
|
|
127
|
+
exit 1
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
$ready = $false
|
|
131
|
+
for ($attempt = 1; $attempt -le $MaxWaitSeconds; $attempt++) {
|
|
132
|
+
try {
|
|
133
|
+
$process.Refresh()
|
|
134
|
+
if ($process.HasExited) {
|
|
135
|
+
Write-Host "[error] Control Center exited before readiness with code $($process.ExitCode)." -ForegroundColor Red
|
|
136
|
+
if (Test-Path -LiteralPath $StderrLog) {
|
|
137
|
+
Get-Content -LiteralPath $StderrLog -Tail 20 -ErrorAction SilentlyContinue
|
|
138
|
+
}
|
|
139
|
+
exit 1
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
$response = Invoke-WebRequest -Uri $ListenUrl -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
|
|
143
|
+
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
|
|
144
|
+
$ready = $true
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
Start-Sleep -Seconds 1
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (-not $ready) {
|
|
153
|
+
Write-Host "[error] Control Center did not become ready on $ListenUrl within $MaxWaitSeconds seconds." -ForegroundColor Red
|
|
154
|
+
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
|
155
|
+
exit 1
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Write-Host "[ok] PatchWarden Control Center restarted at $ListenUrl" -ForegroundColor Green
|
|
159
|
+
Write-Host "[info] node PID $($process.Id)" -ForegroundColor DarkGray
|
|
160
|
+
Write-Host "[info] stdout log: $StdoutLog" -ForegroundColor DarkGray
|
|
161
|
+
Write-Host "[info] stderr log: $StderrLog" -ForegroundColor DarkGray
|
|
162
|
+
|
|
163
|
+
if (-not $NoBrowser) {
|
|
164
|
+
$cacheBuster = Get-Date -Format "yyyyMMddHHmmss"
|
|
165
|
+
Start-Process "$ListenUrl`?restarted=$cacheBuster"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
Assert-PortIsNotForeign
|
|
170
|
+
Stop-ControlCenterProcesses
|
|
171
|
+
Invoke-BuildIfNeeded
|
|
172
|
+
Assert-PortIsNotForeign
|
|
173
|
+
Start-ControlCenter
|
|
@@ -8,7 +8,7 @@ param(
|
|
|
8
8
|
$ErrorActionPreference = "Stop"
|
|
9
9
|
$ProgressPreference = "SilentlyContinue"
|
|
10
10
|
|
|
11
|
-
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
|
11
|
+
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
|
12
12
|
$ConfigPath = Join-Path $ProjectRoot "patchwarden.config.json"
|
|
13
13
|
$RuntimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\runtime"
|
|
14
14
|
|