@agent-teams/engineering-foundation 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/assets/windows-managed-process/WindowsManagedProcess.cs +497 -0
- package/assets/windows-managed-process/bootstrap.ps1 +24 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/node-signal-cancellation-source.d.ts +5 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/node-signal-cancellation-source.d.ts.map +1 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/node-signal-cancellation-source.js +13 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/node-signal-cancellation-source.js.map +1 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/quality-gate-cli.d.ts +13 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/quality-gate-cli.d.ts.map +1 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/quality-gate-cli.js +55 -0
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/quality-gate-cli.js.map +1 -0
- package/dist/capabilities/quality-gate-runner/adapters/outbound/pnpm/pnpm-package-script-executor.d.ts +11 -1
- package/dist/capabilities/quality-gate-runner/adapters/outbound/pnpm/pnpm-package-script-executor.d.ts.map +1 -1
- package/dist/capabilities/quality-gate-runner/adapters/outbound/pnpm/pnpm-package-script-executor.js +15 -8
- package/dist/capabilities/quality-gate-runner/adapters/outbound/pnpm/pnpm-package-script-executor.js.map +1 -1
- package/dist/capabilities/quality-gate-runner/application/ports/package-script-executor.d.ts +7 -0
- package/dist/capabilities/quality-gate-runner/application/ports/package-script-executor.d.ts.map +1 -1
- package/dist/capabilities/quality-gate-runner/application/ports/package-script-executor.js +10 -0
- package/dist/capabilities/quality-gate-runner/application/ports/package-script-executor.js.map +1 -1
- package/dist/capabilities/quality-gate-runner/application/use-cases/run-quality-gate-profile.d.ts.map +1 -1
- package/dist/capabilities/quality-gate-runner/application/use-cases/run-quality-gate-profile.js +22 -10
- package/dist/capabilities/quality-gate-runner/application/use-cases/run-quality-gate-profile.js.map +1 -1
- package/dist/capabilities/quality-gate-runner/contract/config.d.ts +1 -0
- package/dist/capabilities/quality-gate-runner/contract/config.d.ts.map +1 -1
- package/dist/capabilities/quality-gate-runner/contract/config.js.map +1 -1
- package/dist/capabilities/quality-gate-runner/gate-command.d.ts +19 -6
- package/dist/capabilities/quality-gate-runner/gate-command.d.ts.map +1 -1
- package/dist/capabilities/quality-gate-runner/gate-command.js +43 -54
- package/dist/capabilities/quality-gate-runner/gate-command.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/local-mode/process-runner.d.ts +4 -1
- package/dist/local-mode/process-runner.d.ts.map +1 -1
- package/dist/local-mode/process-runner.js +9 -1
- package/dist/local-mode/process-runner.js.map +1 -1
- package/dist/package-self-check.d.ts +2 -2
- package/dist/package-self-check.d.ts.map +1 -1
- package/dist/package-self-check.js +5 -1
- package/dist/package-self-check.js.map +1 -1
- package/dist/process-execution/node-process-runner.d.ts +14 -0
- package/dist/process-execution/node-process-runner.d.ts.map +1 -1
- package/dist/process-execution/node-process-runner.js +129 -57
- package/dist/process-execution/node-process-runner.js.map +1 -1
- package/dist/process-execution/windows-managed-process.d.ts +12 -3
- package/dist/process-execution/windows-managed-process.d.ts.map +1 -1
- package/dist/process-execution/windows-managed-process.js +299 -357
- package/dist/process-execution/windows-managed-process.js.map +1 -1
- package/dist/process-execution/windows-process-host.js +17 -3
- package/dist/process-execution/windows-process-host.js.map +1 -1
- package/dist/quality-gate-cli-command.d.ts +11 -0
- package/dist/quality-gate-cli-command.d.ts.map +1 -1
- package/dist/quality-gate-cli-command.js +76 -30
- package/dist/quality-gate-cli-command.js.map +1 -1
- package/dist/scaffolding/adapters/node/filesystem-authority-recovery.d.ts +6 -0
- package/dist/scaffolding/adapters/node/filesystem-authority-recovery.d.ts.map +1 -0
- package/dist/scaffolding/adapters/node/filesystem-authority-recovery.js +47 -0
- package/dist/scaffolding/adapters/node/filesystem-authority-recovery.js.map +1 -0
- package/dist/scaffolding/adapters/node/filesystem-authority-workspace.d.ts +20 -3
- package/dist/scaffolding/adapters/node/filesystem-authority-workspace.d.ts.map +1 -1
- package/dist/scaffolding/adapters/node/filesystem-authority-workspace.js +18 -40
- package/dist/scaffolding/adapters/node/filesystem-authority-workspace.js.map +1 -1
- package/dist/scaffolding/canonical-api.d.ts +2 -1
- package/dist/scaffolding/canonical-api.d.ts.map +1 -1
- package/dist/scaffolding/canonical-api.js +12 -3
- package/dist/scaffolding/canonical-api.js.map +1 -1
- package/dist/scaffolding/contract/scaffold-contract.d.ts +3 -1
- package/dist/scaffolding/contract/scaffold-contract.d.ts.map +1 -1
- package/dist/scaffolding/contract/types.d.ts +7 -0
- package/dist/scaffolding/contract/types.d.ts.map +1 -1
- package/dist/scaffolding/index.d.ts +2 -2
- package/dist/scaffolding/index.d.ts.map +1 -1
- package/dist/scaffolding/index.js.map +1 -1
- package/dist/scaffolding/kernel/recovery-scope.d.ts +5 -0
- package/dist/scaffolding/kernel/recovery-scope.d.ts.map +1 -0
- package/dist/scaffolding/kernel/recovery-scope.js +94 -0
- package/dist/scaffolding/kernel/recovery-scope.js.map +1 -0
- package/package.json +3 -1
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/report-renderer.d.ts +0 -3
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/report-renderer.d.ts.map +0 -1
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/report-renderer.js +0 -17
- package/dist/capabilities/quality-gate-runner/adapters/inbound/cli/report-renderer.js.map +0 -1
|
@@ -1,390 +1,332 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { performance } from "node:perf_hooks";
|
|
6
|
+
import { join, win32 } from "node:path";
|
|
7
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
const WINDOWS_BOOTSTRAP_PATH = fileURLToPath(new URL("../../assets/windows-managed-process/bootstrap.ps1", import.meta.url));
|
|
3
10
|
const PROCESS_HOST_PATH = fileURLToPath(new URL("./windows-process-host.js", import.meta.url));
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
private const int STD_INPUT_HANDLE = -10;
|
|
25
|
-
private const int STD_OUTPUT_HANDLE = -11;
|
|
26
|
-
private const int STD_ERROR_HANDLE = -12;
|
|
27
|
-
|
|
28
|
-
[StructLayout(LayoutKind.Sequential)]
|
|
29
|
-
private struct PROCESS_INFORMATION
|
|
30
|
-
{
|
|
31
|
-
public IntPtr hProcess;
|
|
32
|
-
public IntPtr hThread;
|
|
33
|
-
public uint dwProcessId;
|
|
34
|
-
public uint dwThreadId;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
38
|
-
private struct STARTUPINFO
|
|
39
|
-
{
|
|
40
|
-
public uint cb;
|
|
41
|
-
public string lpReserved;
|
|
42
|
-
public string lpDesktop;
|
|
43
|
-
public string lpTitle;
|
|
44
|
-
public uint dwX;
|
|
45
|
-
public uint dwY;
|
|
46
|
-
public uint dwXSize;
|
|
47
|
-
public uint dwYSize;
|
|
48
|
-
public uint dwXCountChars;
|
|
49
|
-
public uint dwYCountChars;
|
|
50
|
-
public uint dwFillAttribute;
|
|
51
|
-
public uint dwFlags;
|
|
52
|
-
public ushort wShowWindow;
|
|
53
|
-
public ushort cbReserved2;
|
|
54
|
-
public IntPtr lpReserved2;
|
|
55
|
-
public IntPtr hStdInput;
|
|
56
|
-
public IntPtr hStdOutput;
|
|
57
|
-
public IntPtr hStdError;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
[StructLayout(LayoutKind.Sequential)]
|
|
61
|
-
private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
62
|
-
{
|
|
63
|
-
public long PerProcessUserTimeLimit;
|
|
64
|
-
public long PerJobUserTimeLimit;
|
|
65
|
-
public uint LimitFlags;
|
|
66
|
-
public UIntPtr MinimumWorkingSetSize;
|
|
67
|
-
public UIntPtr MaximumWorkingSetSize;
|
|
68
|
-
public uint ActiveProcessLimit;
|
|
69
|
-
public UIntPtr Affinity;
|
|
70
|
-
public uint PriorityClass;
|
|
71
|
-
public uint SchedulingClass;
|
|
11
|
+
const MAX_WINDOWS_COMMAND_LINE_CHARACTERS = 32_766;
|
|
12
|
+
const CONTROL_CONFIRMATION_TIMEOUT_MS = 30_000;
|
|
13
|
+
const WRAPPER_EXIT_TIMEOUT_MS = 5_000;
|
|
14
|
+
const CONTROL_POLL_INTERVAL_MS = 10;
|
|
15
|
+
const windowsProcessControls = new WeakMap();
|
|
16
|
+
function createControl() {
|
|
17
|
+
const root = mkdtempSync(join(tmpdir(), "agent-teams-foundation-process-"));
|
|
18
|
+
return {
|
|
19
|
+
cancellationPath: join(root, "cancel"),
|
|
20
|
+
confirmationPath: join(root, "contained"),
|
|
21
|
+
launchPath: join(root, "launched"),
|
|
22
|
+
root
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function resolveWindowsPowerShellPath(environment) {
|
|
26
|
+
const systemRoot = Object.entries(environment ?? {}).find(([key]) => key.toLowerCase() === "systemroot")?.[1];
|
|
27
|
+
if (typeof systemRoot !== "string" ||
|
|
28
|
+
systemRoot.length === 0 ||
|
|
29
|
+
!win32.isAbsolute(systemRoot)) {
|
|
30
|
+
throw new Error("SystemRoot must be an absolute Windows path.");
|
|
72
31
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
public long TotalKernelTime;
|
|
79
|
-
public long ThisPeriodTotalUserTime;
|
|
80
|
-
public long ThisPeriodTotalKernelTime;
|
|
81
|
-
public uint TotalPageFaultCount;
|
|
82
|
-
public uint TotalProcesses;
|
|
83
|
-
public uint ActiveProcesses;
|
|
84
|
-
public uint TotalTerminatedProcesses;
|
|
32
|
+
return win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
33
|
+
}
|
|
34
|
+
function commandEnvironmentWithSystemRoot(environment, launcherEnvironment) {
|
|
35
|
+
if (Object.keys(environment).some((key) => key.toLowerCase() === "systemroot")) {
|
|
36
|
+
return environment;
|
|
85
37
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
public ulong ReadTransferCount;
|
|
94
|
-
public ulong WriteTransferCount;
|
|
95
|
-
public ulong OtherTransferCount;
|
|
38
|
+
const systemRoot = Object.entries(launcherEnvironment).find(([key]) => key.toLowerCase() === "systemroot")?.[1];
|
|
39
|
+
return { ...environment, SystemRoot: systemRoot };
|
|
40
|
+
}
|
|
41
|
+
function removeControlRootBestEffort(root) {
|
|
42
|
+
try {
|
|
43
|
+
rmSync(root, { force: true, recursive: true });
|
|
44
|
+
return true;
|
|
96
45
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
100
|
-
{
|
|
101
|
-
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
|
102
|
-
public IO_COUNTERS IoInfo;
|
|
103
|
-
public UIntPtr ProcessMemoryLimit;
|
|
104
|
-
public UIntPtr JobMemoryLimit;
|
|
105
|
-
public UIntPtr PeakProcessMemoryUsed;
|
|
106
|
-
public UIntPtr PeakJobMemoryUsed;
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
107
48
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
113
|
-
private static extern bool SetInformationJobObject(
|
|
114
|
-
IntPtr job,
|
|
115
|
-
int informationClass,
|
|
116
|
-
IntPtr information,
|
|
117
|
-
uint informationLength);
|
|
118
|
-
|
|
119
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
120
|
-
private static extern bool QueryInformationJobObject(
|
|
121
|
-
IntPtr job,
|
|
122
|
-
int informationClass,
|
|
123
|
-
IntPtr information,
|
|
124
|
-
uint informationLength,
|
|
125
|
-
IntPtr returnLength);
|
|
126
|
-
|
|
127
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
128
|
-
private static extern bool TerminateJobObject(IntPtr job, uint exitCode);
|
|
129
|
-
|
|
130
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
131
|
-
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
|
132
|
-
|
|
133
|
-
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
134
|
-
private static extern bool CreateProcess(
|
|
135
|
-
string applicationName,
|
|
136
|
-
StringBuilder commandLine,
|
|
137
|
-
IntPtr processAttributes,
|
|
138
|
-
IntPtr threadAttributes,
|
|
139
|
-
bool inheritHandles,
|
|
140
|
-
uint creationFlags,
|
|
141
|
-
IntPtr environment,
|
|
142
|
-
string currentDirectory,
|
|
143
|
-
ref STARTUPINFO startupInfo,
|
|
144
|
-
out PROCESS_INFORMATION processInformation);
|
|
145
|
-
|
|
146
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
147
|
-
private static extern uint ResumeThread(IntPtr thread);
|
|
148
|
-
|
|
149
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
150
|
-
private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
|
151
|
-
|
|
152
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
153
|
-
private static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode);
|
|
154
|
-
|
|
155
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
156
|
-
private static extern bool TerminateProcess(IntPtr process, uint exitCode);
|
|
157
|
-
|
|
158
|
-
[DllImport("kernel32.dll")]
|
|
159
|
-
private static extern IntPtr GetStdHandle(int standardHandle);
|
|
160
|
-
|
|
161
|
-
[DllImport("kernel32.dll")]
|
|
162
|
-
private static extern bool CloseHandle(IntPtr handle);
|
|
163
|
-
|
|
164
|
-
private static string Quote(string value)
|
|
165
|
-
{
|
|
166
|
-
if (value.Length > 0 && value.IndexOfAny(new[] { ' ', '\t', '"' }) < 0)
|
|
167
|
-
{
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
var output = new StringBuilder("\"");
|
|
171
|
-
var slashes = 0;
|
|
172
|
-
foreach (var character in value)
|
|
173
|
-
{
|
|
174
|
-
if (character == '\\')
|
|
175
|
-
{
|
|
176
|
-
slashes += 1;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (character == '"')
|
|
180
|
-
{
|
|
181
|
-
output.Append('\\', slashes * 2 + 1);
|
|
182
|
-
output.Append(character);
|
|
183
|
-
slashes = 0;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
output.Append('\\', slashes);
|
|
187
|
-
slashes = 0;
|
|
188
|
-
output.Append(character);
|
|
189
|
-
}
|
|
190
|
-
output.Append('\\', slashes * 2);
|
|
191
|
-
output.Append('"');
|
|
192
|
-
return output.ToString();
|
|
49
|
+
}
|
|
50
|
+
function disposeControl(child, control) {
|
|
51
|
+
if (removeControlRootBestEffort(control.root)) {
|
|
52
|
+
windowsProcessControls.delete(child);
|
|
193
53
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
{
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
job,
|
|
204
|
-
JobObjectBasicAccountingInformation,
|
|
205
|
-
pointer,
|
|
206
|
-
(uint)length,
|
|
207
|
-
IntPtr.Zero))
|
|
208
|
-
{
|
|
209
|
-
throw new Win32Exception(
|
|
210
|
-
Marshal.GetLastWin32Error(),
|
|
211
|
-
"QueryInformationJobObject failed");
|
|
212
|
-
}
|
|
213
|
-
accounting = (JOBOBJECT_BASIC_ACCOUNTING_INFORMATION)
|
|
214
|
-
Marshal.PtrToStructure(pointer, typeof(JOBOBJECT_BASIC_ACCOUNTING_INFORMATION));
|
|
215
|
-
return accounting.ActiveProcesses;
|
|
216
|
-
}
|
|
217
|
-
finally
|
|
218
|
-
{
|
|
219
|
-
Marshal.FreeHGlobal(pointer);
|
|
220
|
-
}
|
|
54
|
+
}
|
|
55
|
+
function disposeControlAfterWrapperExit(child, control) {
|
|
56
|
+
const disposeAfterExit = () => {
|
|
57
|
+
disposeControl(child, control);
|
|
58
|
+
};
|
|
59
|
+
child.once("exit", disposeAfterExit);
|
|
60
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
61
|
+
child.removeListener("exit", disposeAfterExit);
|
|
62
|
+
disposeAfterExit();
|
|
221
63
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
throw new Win32Exception(Marshal.GetLastWin32Error(), "TerminateJobObject failed");
|
|
228
|
-
}
|
|
229
|
-
var stopwatch = Stopwatch.StartNew();
|
|
230
|
-
while (ActiveProcessCount(job) > 0)
|
|
231
|
-
{
|
|
232
|
-
if (stopwatch.ElapsedMilliseconds >= 5000)
|
|
233
|
-
{
|
|
234
|
-
throw new TimeoutException(
|
|
235
|
-
"Windows Job Object descendants did not terminate within 5000 ms");
|
|
236
|
-
}
|
|
237
|
-
Thread.Sleep(10);
|
|
238
|
-
}
|
|
64
|
+
}
|
|
65
|
+
function quoteWindowsArgument(value) {
|
|
66
|
+
if (value.length > 0 && !/[ \t"]/u.test(value)) {
|
|
67
|
+
return value;
|
|
239
68
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
{
|
|
247
|
-
var job = CreateJobObject(IntPtr.Zero, null);
|
|
248
|
-
if (job == IntPtr.Zero)
|
|
249
|
-
{
|
|
250
|
-
throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateJobObject failed");
|
|
69
|
+
let output = '"';
|
|
70
|
+
let slashes = 0;
|
|
71
|
+
for (const character of value) {
|
|
72
|
+
if (character === "\\") {
|
|
73
|
+
slashes += 1;
|
|
74
|
+
continue;
|
|
251
75
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
257
|
-
var length = Marshal.SizeOf(limits);
|
|
258
|
-
var limitsPointer = Marshal.AllocHGlobal(length);
|
|
259
|
-
try
|
|
260
|
-
{
|
|
261
|
-
Marshal.StructureToPtr(limits, limitsPointer, false);
|
|
262
|
-
if (!SetInformationJobObject(
|
|
263
|
-
job,
|
|
264
|
-
JobObjectExtendedLimitInformation,
|
|
265
|
-
limitsPointer,
|
|
266
|
-
(uint)length))
|
|
267
|
-
{
|
|
268
|
-
throw new Win32Exception(
|
|
269
|
-
Marshal.GetLastWin32Error(),
|
|
270
|
-
"SetInformationJobObject failed");
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
finally
|
|
274
|
-
{
|
|
275
|
-
Marshal.FreeHGlobal(limitsPointer);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
var startup = new STARTUPINFO();
|
|
279
|
-
startup.cb = (uint)Marshal.SizeOf(startup);
|
|
280
|
-
startup.dwFlags = STARTF_USESTDHANDLES;
|
|
281
|
-
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
282
|
-
startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
283
|
-
startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
284
|
-
var commandLine = new StringBuilder(
|
|
285
|
-
Quote(executable) + " " + Quote(hostPath) + " " + Quote(encodedRequest));
|
|
286
|
-
if (!CreateProcess(
|
|
287
|
-
executable,
|
|
288
|
-
commandLine,
|
|
289
|
-
IntPtr.Zero,
|
|
290
|
-
IntPtr.Zero,
|
|
291
|
-
true,
|
|
292
|
-
CREATE_SUSPENDED,
|
|
293
|
-
IntPtr.Zero,
|
|
294
|
-
currentDirectory,
|
|
295
|
-
ref startup,
|
|
296
|
-
out process))
|
|
297
|
-
{
|
|
298
|
-
throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateProcess failed");
|
|
299
|
-
}
|
|
300
|
-
if (!AssignProcessToJobObject(job, process.hProcess))
|
|
301
|
-
{
|
|
302
|
-
var errorCode = Marshal.GetLastWin32Error();
|
|
303
|
-
TerminateProcess(process.hProcess, 1);
|
|
304
|
-
throw new Win32Exception(
|
|
305
|
-
errorCode,
|
|
306
|
-
"AssignProcessToJobObject failed");
|
|
307
|
-
}
|
|
308
|
-
if (ResumeThread(process.hThread) == 0xffffffff)
|
|
309
|
-
{
|
|
310
|
-
var errorCode = Marshal.GetLastWin32Error();
|
|
311
|
-
TerminateProcess(process.hProcess, 1);
|
|
312
|
-
throw new Win32Exception(errorCode, "ResumeThread failed");
|
|
313
|
-
}
|
|
314
|
-
if (WaitForSingleObject(process.hProcess, INFINITE) == 0xffffffff)
|
|
315
|
-
{
|
|
316
|
-
var errorCode = Marshal.GetLastWin32Error();
|
|
317
|
-
TerminateProcess(process.hProcess, 1);
|
|
318
|
-
throw new Win32Exception(errorCode, "WaitForSingleObject failed");
|
|
319
|
-
}
|
|
320
|
-
uint exitCode;
|
|
321
|
-
if (!GetExitCodeProcess(process.hProcess, out exitCode))
|
|
322
|
-
{
|
|
323
|
-
throw new Win32Exception(Marshal.GetLastWin32Error(), "GetExitCodeProcess failed");
|
|
324
|
-
}
|
|
325
|
-
TerminateRemainingProcessesAndWait(job);
|
|
326
|
-
return unchecked((int)exitCode);
|
|
327
|
-
}
|
|
328
|
-
finally
|
|
329
|
-
{
|
|
330
|
-
if (process.hThread != IntPtr.Zero) CloseHandle(process.hThread);
|
|
331
|
-
if (process.hProcess != IntPtr.Zero) CloseHandle(process.hProcess);
|
|
332
|
-
CloseHandle(job);
|
|
76
|
+
if (character === '"') {
|
|
77
|
+
output += "\\".repeat(slashes * 2 + 1) + character;
|
|
78
|
+
slashes = 0;
|
|
79
|
+
continue;
|
|
333
80
|
}
|
|
81
|
+
output += "\\".repeat(slashes) + character;
|
|
82
|
+
slashes = 0;
|
|
334
83
|
}
|
|
84
|
+
return output + "\\".repeat(slashes * 2) + '"';
|
|
335
85
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
(Get-Location).Path,
|
|
344
|
-
[string]$bootstrapRequest.encodedRequest)
|
|
345
|
-
exit $exitCode
|
|
346
|
-
} catch {
|
|
347
|
-
[Console]::Error.WriteLine("Windows Job Object runner failed: " + $_.Exception.Message)
|
|
348
|
-
exit 1
|
|
86
|
+
function assertWindowsCommandLineFits(executable, args) {
|
|
87
|
+
const commandLine = [executable, ...args]
|
|
88
|
+
.map((argument) => quoteWindowsArgument(argument))
|
|
89
|
+
.join(" ");
|
|
90
|
+
if (commandLine.length > MAX_WINDOWS_COMMAND_LINE_CHARACTERS) {
|
|
91
|
+
throw new Error(`The managed Windows process bootstrap command line exceeds ${MAX_WINDOWS_COMMAND_LINE_CHARACTERS} characters.`);
|
|
92
|
+
}
|
|
349
93
|
}
|
|
350
|
-
`;
|
|
351
|
-
const ENCODED_WINDOWS_JOB_RUNNER = Buffer.from(WINDOWS_JOB_RUNNER, "utf16le").toString("base64");
|
|
352
94
|
/**
|
|
353
|
-
* Starts
|
|
354
|
-
*
|
|
355
|
-
*
|
|
95
|
+
* Starts the packaged PowerShell bootstrap with a fixed, short command line.
|
|
96
|
+
* The request crosses the PowerShell boundary only as JSON on stdin. The
|
|
97
|
+
* native helper atomically creates the Node process host in its Job Object;
|
|
98
|
+
* only that contained host can launch the requested command.
|
|
356
99
|
*/
|
|
357
100
|
export function spawnWindowsManagedProcess(request) {
|
|
101
|
+
const control = createControl();
|
|
102
|
+
const launcherEnvironment = request.launcherEnvironment ?? process.env;
|
|
103
|
+
const commandEnvironment = commandEnvironmentWithSystemRoot(request.environment ?? launcherEnvironment, launcherEnvironment);
|
|
358
104
|
const encodedRequest = Buffer.from(JSON.stringify({
|
|
359
105
|
schemaVersion: 1,
|
|
360
106
|
command: request.command,
|
|
361
107
|
args: [...request.args],
|
|
362
|
-
cwd: request.cwd
|
|
108
|
+
cwd: request.cwd,
|
|
109
|
+
launchPath: control.launchPath
|
|
363
110
|
})).toString("base64url");
|
|
364
|
-
|
|
365
|
-
|
|
111
|
+
try {
|
|
112
|
+
assertWindowsCommandLineFits(process.execPath, [PROCESS_HOST_PATH, encodedRequest]);
|
|
366
113
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
114
|
+
catch (error) {
|
|
115
|
+
removeControlRootBestEffort(control.root);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
let child;
|
|
119
|
+
try {
|
|
120
|
+
child = spawn(resolveWindowsPowerShellPath(launcherEnvironment), [
|
|
121
|
+
"-NoLogo",
|
|
122
|
+
"-NoProfile",
|
|
123
|
+
"-NonInteractive",
|
|
124
|
+
"-ExecutionPolicy",
|
|
125
|
+
"Bypass",
|
|
126
|
+
"-File",
|
|
127
|
+
WINDOWS_BOOTSTRAP_PATH
|
|
128
|
+
], {
|
|
129
|
+
cwd: request.cwd,
|
|
130
|
+
env: launcherEnvironment,
|
|
131
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
132
|
+
windowsHide: true
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
removeControlRootBestEffort(control.root);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
windowsProcessControls.set(child, control);
|
|
140
|
+
child.once("error", () => {
|
|
141
|
+
cleanUpWindowsManagedProcessLaunchFailure(child);
|
|
379
142
|
});
|
|
380
|
-
child.stdin
|
|
143
|
+
const bootstrapInput = child.stdin;
|
|
144
|
+
if (bootstrapInput === null) {
|
|
145
|
+
try {
|
|
146
|
+
child.kill("SIGKILL");
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
disposeControlAfterWrapperExit(child, control);
|
|
150
|
+
}
|
|
151
|
+
throw new Error("Windows Job Object wrapper did not expose its bootstrap input.");
|
|
152
|
+
}
|
|
153
|
+
bootstrapInput.once("error", () => {
|
|
381
154
|
// The wrapper's process error is reported through its own error event.
|
|
382
155
|
});
|
|
383
|
-
|
|
156
|
+
bootstrapInput.end(JSON.stringify({
|
|
157
|
+
schemaVersion: 1,
|
|
384
158
|
nodeExecutable: process.execPath,
|
|
385
159
|
processHostPath: PROCESS_HOST_PATH,
|
|
386
|
-
encodedRequest
|
|
160
|
+
encodedRequest,
|
|
161
|
+
cwd: request.cwd,
|
|
162
|
+
environmentEntries: Object.entries(commandEnvironment)
|
|
163
|
+
.filter((entry) => entry[1] !== undefined)
|
|
164
|
+
.map(([key, value]) => `${key}=${value}`),
|
|
165
|
+
cancellationPath: control.cancellationPath,
|
|
166
|
+
confirmationPath: control.confirmationPath,
|
|
167
|
+
launchPath: control.launchPath
|
|
387
168
|
}));
|
|
388
169
|
return child;
|
|
389
170
|
}
|
|
171
|
+
/** Releases control artifacts when PowerShell was never launched. */
|
|
172
|
+
export function cleanUpWindowsManagedProcessLaunchFailure(child) {
|
|
173
|
+
const control = windowsProcessControls.get(child);
|
|
174
|
+
if (child.pid === undefined && control !== undefined) {
|
|
175
|
+
disposeControl(child, control);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function readContainmentConfirmation(control) {
|
|
179
|
+
try {
|
|
180
|
+
return await readFile(control.confirmationPath, "utf8");
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function controlFor(child) {
|
|
190
|
+
const control = windowsProcessControls.get(child);
|
|
191
|
+
if (control === undefined) {
|
|
192
|
+
throw new Error("The Windows process is not owned by the managed-process adapter.");
|
|
193
|
+
}
|
|
194
|
+
return control;
|
|
195
|
+
}
|
|
196
|
+
async function wrapperExitedWithin(child, timeoutMs) {
|
|
197
|
+
const exitDeadline = performance.now() + timeoutMs;
|
|
198
|
+
while (child.exitCode === null && child.signalCode === null) {
|
|
199
|
+
const remainingMs = exitDeadline - performance.now();
|
|
200
|
+
if (remainingMs <= 0) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
await delay(Math.min(CONTROL_POLL_INTERVAL_MS, remainingMs));
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
async function forceAndWaitForWrapperExit(child, control) {
|
|
208
|
+
const cleanupErrors = [];
|
|
209
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
210
|
+
let terminationRequested = false;
|
|
211
|
+
try {
|
|
212
|
+
terminationRequested = child.kill("SIGKILL");
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
cleanupErrors.push(error);
|
|
216
|
+
}
|
|
217
|
+
if (!await wrapperExitedWithin(child, WRAPPER_EXIT_TIMEOUT_MS)) {
|
|
218
|
+
if (!terminationRequested && cleanupErrors.length === 0) {
|
|
219
|
+
cleanupErrors.push(new Error("Windows Job Object wrapper could not be terminated."));
|
|
220
|
+
}
|
|
221
|
+
cleanupErrors.push(new Error(`Windows Job Object wrapper did not exit within ${String(WRAPPER_EXIT_TIMEOUT_MS)} ms after forced termination.`));
|
|
222
|
+
try {
|
|
223
|
+
disposeControlAfterWrapperExit(child, control);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
cleanupErrors.push(error);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
try {
|
|
231
|
+
disposeControl(child, control);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
cleanupErrors.push(error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
try {
|
|
240
|
+
disposeControl(child, control);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
cleanupErrors.push(error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (cleanupErrors.length === 1) {
|
|
247
|
+
throw cleanupErrors[0];
|
|
248
|
+
}
|
|
249
|
+
if (cleanupErrors.length > 1) {
|
|
250
|
+
const aggregateError = new AggregateError(cleanupErrors, "Windows Job Object wrapper termination produced multiple cleanup errors.");
|
|
251
|
+
aggregateError.cause = cleanupErrors.at(-1);
|
|
252
|
+
throw aggregateError;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function failAfterForcingWrapperExit(child, control, originalError, aggregateMessage) {
|
|
256
|
+
try {
|
|
257
|
+
await forceAndWaitForWrapperExit(child, control);
|
|
258
|
+
}
|
|
259
|
+
catch (cleanupError) {
|
|
260
|
+
const cleanupErrors = cleanupError instanceof AggregateError ?
|
|
261
|
+
cleanupError.errors :
|
|
262
|
+
[cleanupError];
|
|
263
|
+
const aggregateError = new AggregateError([originalError, ...cleanupErrors], aggregateMessage);
|
|
264
|
+
aggregateError.cause = cleanupErrors.at(-1);
|
|
265
|
+
throw aggregateError;
|
|
266
|
+
}
|
|
267
|
+
throw originalError;
|
|
268
|
+
}
|
|
269
|
+
export async function requestWindowsManagedProcessTermination(child) {
|
|
270
|
+
const control = controlFor(child);
|
|
271
|
+
try {
|
|
272
|
+
await writeFile(control.cancellationPath, "CANCEL", { encoding: "utf8" });
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
await failAfterForcingWrapperExit(child, control, error, "Windows cancellation marker creation and wrapper cleanup both failed.");
|
|
276
|
+
}
|
|
277
|
+
await waitForWindowsManagedProcessContainment(child);
|
|
278
|
+
}
|
|
279
|
+
export async function waitForWindowsManagedProcessContainment(child, confirmationTimeoutMs = CONTROL_CONFIRMATION_TIMEOUT_MS) {
|
|
280
|
+
if (!Number.isSafeInteger(confirmationTimeoutMs) || confirmationTimeoutMs <= 0) {
|
|
281
|
+
throw new TypeError("The Windows containment timeout must be a positive safe integer.");
|
|
282
|
+
}
|
|
283
|
+
const control = controlFor(child);
|
|
284
|
+
const deadline = performance.now() + confirmationTimeoutMs;
|
|
285
|
+
for (;;) {
|
|
286
|
+
let confirmation;
|
|
287
|
+
try {
|
|
288
|
+
confirmation = await readContainmentConfirmation(control);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
await failAfterForcingWrapperExit(child, control, error, "Windows containment confirmation and wrapper cleanup both failed.");
|
|
292
|
+
}
|
|
293
|
+
if (confirmation !== undefined) {
|
|
294
|
+
if (confirmation !== "CONTAINED") {
|
|
295
|
+
await failAfterForcingWrapperExit(child, control, new Error("Windows Job Object wrapper sent an invalid containment confirmation."), "Windows containment confirmation and wrapper cleanup both failed.");
|
|
296
|
+
}
|
|
297
|
+
if (await wrapperExitedWithin(child, WRAPPER_EXIT_TIMEOUT_MS)) {
|
|
298
|
+
disposeControl(child, control);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await forceAndWaitForWrapperExit(child, control);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
305
|
+
// The wrapper publishes the marker immediately before it exits. If exit
|
|
306
|
+
// wins the poll race, perform one bounded final read before retiring the
|
|
307
|
+
// control directory so a durable confirmation is not discarded unseen.
|
|
308
|
+
try {
|
|
309
|
+
confirmation = await readContainmentConfirmation(control);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
await failAfterForcingWrapperExit(child, control, error, "Windows containment confirmation and wrapper cleanup both failed.");
|
|
313
|
+
}
|
|
314
|
+
if (confirmation === "CONTAINED") {
|
|
315
|
+
disposeControl(child, control);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (confirmation !== undefined) {
|
|
319
|
+
await failAfterForcingWrapperExit(child, control, new Error("Windows Job Object wrapper sent an invalid containment confirmation."), "Windows containment confirmation and wrapper cleanup both failed.");
|
|
320
|
+
}
|
|
321
|
+
disposeControl(child, control);
|
|
322
|
+
throw new Error("Windows Job Object wrapper exited before it confirmed process containment.");
|
|
323
|
+
}
|
|
324
|
+
const remainingMs = deadline - performance.now();
|
|
325
|
+
if (remainingMs <= 0) {
|
|
326
|
+
const timeoutError = new Error(`Windows Job Object wrapper did not confirm containment within ${String(confirmationTimeoutMs)} ms.`);
|
|
327
|
+
await failAfterForcingWrapperExit(child, control, timeoutError, "Windows Job Object wrapper timed out and could not be terminated.");
|
|
328
|
+
}
|
|
329
|
+
await delay(Math.min(CONTROL_POLL_INTERVAL_MS, remainingMs));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
390
332
|
//# sourceMappingURL=windows-managed-process.js.map
|