@automaton-labs/aib 0.0.6 → 0.0.7
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/dist/bin/cli.js +4 -2
- package/dist/commands/apply-module-plan.js +3 -3
- package/dist/commands/background-command.js +2 -0
- package/dist/commands/captured-input.js +1 -2
- package/dist/commands/config-command.js +2 -2
- package/dist/commands/config-output.js +10 -10
- package/dist/commands/control-plane-command.js +1 -0
- package/dist/commands/declaration-command.js +7 -0
- package/dist/commands/declaration-execution-cache.js +1 -0
- package/dist/commands/declaration-format.js +1 -0
- package/dist/commands/declaration-preview-cache.js +2 -0
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/execution-command.js +1 -1
- package/dist/commands/file-command.js +1 -0
- package/dist/commands/file-move-to-dir-execution-cache.js +1 -0
- package/dist/commands/file-move-to-dir-preview-cache.js +2 -0
- package/dist/commands/file-move-to-dir.js +5 -0
- package/dist/commands/file-mutation-runtime.js +1 -0
- package/dist/commands/file-refactor-batch-execution-cache.js +1 -0
- package/dist/commands/file-refactor-batch-preview-cache.js +2 -0
- package/dist/commands/file-refactor-batch.js +5 -0
- package/dist/commands/file-rename-execution-cache.js +1 -0
- package/dist/commands/file-rename-preview-cache.js +2 -0
- package/dist/commands/file-rename.js +5 -0
- package/dist/commands/help-command.js +5 -5
- package/dist/commands/host-command.js +1 -0
- package/dist/commands/inspect-cycles.js +3 -3
- package/dist/commands/inspect-format.js +15 -15
- package/dist/commands/inspect-tree.js +2 -2
- package/dist/commands/inspect.js +12 -12
- package/dist/commands/move-to-file.js +2 -2
- package/dist/commands/ota-command.js +2 -0
- package/dist/commands/quick-read.js +4 -4
- package/dist/commands/refactor-batch.js +4 -4
- package/dist/commands/rename-entities.js +4 -4
- package/dist/commands/result-view.js +6 -5
- package/dist/commands/worker-command.js +1 -0
- package/dist/config/defaults.js +1 -1
- package/dist/config/env-vars.js +1 -1
- package/dist/config/path-aliases.js +1 -1
- package/dist/config/resolve-command-alias.js +1 -1
- package/dist/content/content-bundle.js +1 -1
- package/dist/dsl/aib-dsl.js +1 -1
- package/dist/dsl/declaration-dsl.js +1 -0
- package/dist/dsl/file-move-to-dir-dsl.js +1 -0
- package/dist/dsl/file-refactor-batch-dsl.js +1 -0
- package/dist/dsl/file-rename-dsl.js +1 -0
- package/dist/help/{json/bootstrap.md → bootstrap.bash.md} +214 -392
- package/dist/help/bootstrap.md +101 -137
- package/dist/help/{json/full.md → bootstrap.pwsh.md} +214 -392
- package/dist/help/docs/dsl.md +4 -3
- package/dist/help/docs/file.move.md +46 -0
- package/dist/help/docs/file.rename.md +35 -0
- package/dist/help/docs/inspect.code.md +5 -5
- package/dist/help/docs/inspect.deps.md +7 -7
- package/dist/help/docs/inspect.file.md +2 -1
- package/dist/help/docs/inspect.graph.md +3 -3
- package/dist/help/docs/inspect.md +5 -5
- package/dist/help/docs/mutation.md +23 -13
- package/dist/help/docs/patterns.md +1 -5
- package/dist/help/docs/qr.md +7 -22
- package/dist/help/docs/quick-map.md +1 -0
- package/dist/help/docs/recipe.file-layout.md +15 -0
- package/dist/help/docs/rg.md +22 -0
- package/dist/help/docs/selectors.md +3 -2
- package/dist/help/docs/session.md +1 -1
- package/dist/help/dsl/bootstrap.bash.md +907 -0
- package/dist/help/dsl/bootstrap.md +101 -137
- package/dist/help/dsl/bootstrap.pwsh.md +907 -0
- package/dist/help/dsl/docs/dsl.md +4 -3
- package/dist/help/dsl/docs/file.move.md +46 -0
- package/dist/help/dsl/docs/file.rename.md +35 -0
- package/dist/help/dsl/docs/inspect.code.md +5 -5
- package/dist/help/dsl/docs/inspect.deps.md +7 -7
- package/dist/help/dsl/docs/inspect.file.md +2 -1
- package/dist/help/dsl/docs/inspect.graph.md +3 -3
- package/dist/help/dsl/docs/inspect.md +5 -5
- package/dist/help/dsl/docs/mutation.md +23 -13
- package/dist/help/dsl/docs/patterns.md +1 -5
- package/dist/help/dsl/docs/qr.md +7 -22
- package/dist/help/dsl/docs/quick-map.md +1 -0
- package/dist/help/dsl/docs/recipe.file-layout.md +15 -0
- package/dist/help/dsl/docs/rg.md +22 -0
- package/dist/help/dsl/docs/selectors.md +3 -2
- package/dist/help/dsl/docs/session.md +1 -1
- package/dist/help/dsl/full.md +101 -137
- package/dist/help/dsl/help-index.md +44 -0
- package/dist/help/dsl/tokens/qr.inline.examples.md +2 -0
- package/dist/help/dsl/tokens/stdin.examples.bash.md +8 -0
- package/dist/help/dsl/tokens/stdin.examples.pwsh.md +8 -0
- package/dist/help/dsl/tokens/stdin.examples.with-qr.bash.md +12 -0
- package/dist/help/dsl/tokens/stdin.examples.with-qr.pwsh.md +12 -0
- package/dist/help/dsl/topics/dsl.md +4 -3
- package/dist/help/dsl/topics/file.move.md +44 -0
- package/dist/help/dsl/topics/file.rename.md +33 -0
- package/dist/help/dsl/topics/inspect.code.md +5 -5
- package/dist/help/dsl/topics/inspect.deps.md +7 -7
- package/dist/help/dsl/topics/inspect.file.md +2 -1
- package/dist/help/dsl/topics/inspect.graph.md +3 -3
- package/dist/help/dsl/topics/inspect.md +5 -5
- package/dist/help/dsl/topics/mutation.md +23 -13
- package/dist/help/dsl/topics/patterns.md +1 -5
- package/dist/help/dsl/topics/qr.md +7 -22
- package/dist/help/dsl/topics/quick-map.md +1 -0
- package/dist/help/dsl/topics/recipe.file-layout.md +13 -0
- package/dist/help/dsl/topics/rg.md +20 -0
- package/dist/help/dsl/topics/selectors.md +3 -2
- package/dist/help/dsl/topics/session.md +1 -1
- package/dist/help/full.md +101 -137
- package/dist/help/help-index.md +44 -0
- package/dist/help/help-meta.json +43 -27
- package/dist/help/index.md +32 -75
- package/dist/help/tokens/qr.inline.examples.md +2 -0
- package/dist/help/tokens/stdin.examples.bash.md +8 -0
- package/dist/help/tokens/stdin.examples.pwsh.md +8 -0
- package/dist/help/tokens/stdin.examples.with-qr.bash.md +12 -0
- package/dist/help/tokens/stdin.examples.with-qr.pwsh.md +12 -0
- package/dist/help/topics/dsl.md +4 -3
- package/dist/help/topics/file.move.md +44 -0
- package/dist/help/topics/file.rename.md +33 -0
- package/dist/help/topics/inspect.code.md +5 -5
- package/dist/help/topics/inspect.deps.md +7 -7
- package/dist/help/topics/inspect.file.md +2 -1
- package/dist/help/topics/inspect.graph.md +3 -3
- package/dist/help/topics/inspect.md +5 -5
- package/dist/help/topics/mutation.md +23 -13
- package/dist/help/topics/patterns.md +1 -5
- package/dist/help/topics/qr.md +7 -22
- package/dist/help/topics/quick-map.md +1 -0
- package/dist/help/topics/recipe.file-layout.md +13 -0
- package/dist/help/topics/rg.md +20 -0
- package/dist/help/topics/selectors.md +3 -2
- package/dist/help/topics/session.md +1 -1
- package/dist/host/capabilities.js +1 -0
- package/dist/ide-launch/common.cjs +162 -162
- package/dist/managed-host/extension-vsix-resolver.js +1 -1
- package/dist/managed-host/manage-serve-web-host.cjs +141 -141
- package/dist/managed-host/serve-web-autostart.js +1 -1
- package/dist/managed-host/serve-web-host.cjs +1781 -1570
- package/dist/payloads/read-stdin-json.js +1 -1
- package/dist/rg-wrapper/core.js +6 -0
- package/dist/rg-wrapper/options.js +1 -0
- package/dist/rtk-wrapper/core.js +2 -0
- package/dist/runtime/input-source.js +1 -1
- package/dist/runtime/run-command.js +1 -1
- package/dist/selectors/parse-entities.js +1 -1
- package/dist/session/daemon-state.js +2 -0
- package/dist/session/server.js +7 -8
- package/dist/shared/operations.js +1 -1
- package/dist/shared/protocol.js +1 -1
- package/dist/shared/routes.js +1 -1
- package/dist/shared/stdin-examples.js +1 -3
- package/dist/shared/types.js +1 -1
- package/dist/tool-runtime/runtime-tools.js +1 -0
- package/dist/workers/control-plane/aib-control-plane-worker.cjs +80 -0
- package/dist/workers/control-plane/client-config.default.json +3 -0
- package/dist/workers/product-storage.js +1 -0
- package/dist/workers/registry.js +1 -0
- package/extension/vscode-refactor-bridge-extension.vsix +0 -0
- package/package.json +6 -4
- package/runtimes/launcher/linux-x64/aib +0 -0
- package/runtimes/launcher/win-x64/aib.exe +0 -0
- package/runtimes/tools/rg/darwin-arm64/rg +0 -0
- package/runtimes/tools/rg/darwin-x64/rg +0 -0
- package/runtimes/tools/rg/linux-arm64/rg +0 -0
- package/runtimes/tools/rg/linux-x64/rg +0 -0
- package/runtimes/tools/rg/manifest.json +36 -0
- package/runtimes/tools/rg/win32-x64/rg.exe +0 -0
- package/runtimes/tools/rtk/darwin-arm64/rtk +0 -0
- package/runtimes/tools/rtk/darwin-x64/rtk +0 -0
- package/runtimes/tools/rtk/linux-arm64/rtk +0 -0
- package/runtimes/tools/rtk/linux-x64/rtk +0 -0
- package/runtimes/tools/rtk/manifest.json +36 -0
- package/runtimes/tools/rtk/win32-x64/rtk.exe +0 -0
- package/dist/help/json/docs/basics.md +0 -15
- package/dist/help/json/docs/config-setup.aliases.md +0 -56
- package/dist/help/json/docs/config-setup.excludes.md +0 -45
- package/dist/help/json/docs/config-setup.import-normalize.md +0 -86
- package/dist/help/json/docs/config-setup.import-rules.md +0 -96
- package/dist/help/json/docs/config-setup.tsconfigs.md +0 -33
- package/dist/help/json/docs/dsl.md +0 -25
- package/dist/help/json/docs/first-setup.md +0 -292
- package/dist/help/json/docs/help-format.md +0 -9
- package/dist/help/json/docs/help-protocol.md +0 -26
- package/dist/help/json/docs/imports.normalize.md +0 -47
- package/dist/help/json/docs/inspect.code.md +0 -41
- package/dist/help/json/docs/inspect.cycles.md +0 -38
- package/dist/help/json/docs/inspect.deps.md +0 -46
- package/dist/help/json/docs/inspect.duplicates.md +0 -65
- package/dist/help/json/docs/inspect.exports.md +0 -40
- package/dist/help/json/docs/inspect.file.md +0 -38
- package/dist/help/json/docs/inspect.graph.md +0 -139
- package/dist/help/json/docs/inspect.imports.md +0 -15
- package/dist/help/json/docs/inspect.md +0 -88
- package/dist/help/json/docs/inspect.members.md +0 -32
- package/dist/help/json/docs/inspect.tree.md +0 -32
- package/dist/help/json/docs/inspect.usages.md +0 -61
- package/dist/help/json/docs/modulePlan.md +0 -73
- package/dist/help/json/docs/move.md +0 -56
- package/dist/help/json/docs/mutation.md +0 -65
- package/dist/help/json/docs/patterns.md +0 -76
- package/dist/help/json/docs/prefs.md +0 -40
- package/dist/help/json/docs/qr.md +0 -37
- package/dist/help/json/docs/quick-map.md +0 -30
- package/dist/help/json/docs/refactor.batch.md +0 -72
- package/dist/help/json/docs/rename.md +0 -47
- package/dist/help/json/docs/reveal.md +0 -30
- package/dist/help/json/docs/selectors.md +0 -23
- package/dist/help/json/docs/session.md +0 -77
- package/dist/help/json/docs/view.md +0 -30
- package/dist/help/json/snippets/agents.md +0 -14
- package/dist/help/json/topics/basics.md +0 -13
- package/dist/help/json/topics/config-setup.aliases.md +0 -54
- package/dist/help/json/topics/config-setup.excludes.md +0 -43
- package/dist/help/json/topics/config-setup.import-normalize.md +0 -84
- package/dist/help/json/topics/config-setup.import-rules.md +0 -94
- package/dist/help/json/topics/config-setup.tsconfigs.md +0 -31
- package/dist/help/json/topics/dsl.md +0 -23
- package/dist/help/json/topics/first-setup.md +0 -290
- package/dist/help/json/topics/help-format.md +0 -7
- package/dist/help/json/topics/help-protocol.md +0 -24
- package/dist/help/json/topics/imports.normalize.md +0 -45
- package/dist/help/json/topics/inspect.code.md +0 -39
- package/dist/help/json/topics/inspect.cycles.md +0 -36
- package/dist/help/json/topics/inspect.deps.md +0 -44
- package/dist/help/json/topics/inspect.duplicates.md +0 -37
- package/dist/help/json/topics/inspect.exports.md +0 -38
- package/dist/help/json/topics/inspect.file.md +0 -36
- package/dist/help/json/topics/inspect.graph.md +0 -137
- package/dist/help/json/topics/inspect.imports.md +0 -13
- package/dist/help/json/topics/inspect.md +0 -86
- package/dist/help/json/topics/inspect.members.md +0 -30
- package/dist/help/json/topics/inspect.tree.md +0 -22
- package/dist/help/json/topics/inspect.usages.md +0 -59
- package/dist/help/json/topics/modulePlan.md +0 -60
- package/dist/help/json/topics/move.md +0 -46
- package/dist/help/json/topics/mutation.md +0 -63
- package/dist/help/json/topics/patterns.md +0 -74
- package/dist/help/json/topics/prefs.md +0 -38
- package/dist/help/json/topics/qr.md +0 -35
- package/dist/help/json/topics/quick-map.md +0 -28
- package/dist/help/json/topics/refactor.batch.md +0 -61
- package/dist/help/json/topics/rename.md +0 -42
- package/dist/help/json/topics/reveal.md +0 -28
- package/dist/help/json/topics/selectors.md +0 -21
- package/dist/help/json/topics/session.md +0 -59
- package/dist/help/json/topics/view.md +0 -28
|
@@ -1,344 +1,432 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const crypto = require("node:crypto");
|
|
4
|
-
const fs = require("node:fs");
|
|
5
|
-
const net = require("node:net");
|
|
6
|
-
const os = require("node:os");
|
|
7
|
-
const path = require("node:path");
|
|
8
|
-
const { spawn, spawnSync } = require("node:child_process");
|
|
9
|
-
const {
|
|
10
|
-
defaultFixtureDir,
|
|
11
|
-
defaultVsixPath,
|
|
12
|
-
prepareSpawnCommand,
|
|
13
|
-
readRegistryEntries,
|
|
14
|
-
resolveIdeCommand,
|
|
15
|
-
rootDir,
|
|
16
|
-
run,
|
|
17
|
-
sleep,
|
|
18
|
-
timestampForPath,
|
|
19
|
-
writeJson
|
|
20
|
-
} = require("../ide-launch/common.cjs");
|
|
21
|
-
|
|
22
|
-
const managedRootDir = path.join(os.tmpdir(), "aib", "managed-hosts");
|
|
23
|
-
const defaultRunsDir = path.join(rootDir, ".tmp", "managed-host-runs");
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
fs.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
fs.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const net = require("node:net");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
9
|
+
const {
|
|
10
|
+
defaultFixtureDir,
|
|
11
|
+
defaultVsixPath,
|
|
12
|
+
prepareSpawnCommand,
|
|
13
|
+
readRegistryEntries,
|
|
14
|
+
resolveIdeCommand,
|
|
15
|
+
rootDir,
|
|
16
|
+
run,
|
|
17
|
+
sleep,
|
|
18
|
+
timestampForPath,
|
|
19
|
+
writeJson
|
|
20
|
+
} = require("../ide-launch/common.cjs");
|
|
21
|
+
|
|
22
|
+
const managedRootDir = path.join(os.tmpdir(), "aib", "managed-hosts");
|
|
23
|
+
const defaultRunsDir = path.join(rootDir, ".tmp", "managed-host-runs");
|
|
24
|
+
const repoRootDir = findRepoRoot(rootDir);
|
|
25
|
+
const extensionPackageDir = repoRootDir ? path.join(repoRootDir, "packages", "extension") : null;
|
|
26
|
+
const repoDefaultVsixPath = repoRootDir ? path.join(repoRootDir, ".tmp", "vscode-refactor-bridge-extension.vsix") : null;
|
|
27
|
+
|
|
28
|
+
function normalizeForId(value) {
|
|
29
|
+
return path.resolve(String(value)).replace(/[\\/]+$/, "").toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeForCompare(value) {
|
|
33
|
+
return path.normalize(String(value)).replace(/[\\/]+$/, "").toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hashShort(value) {
|
|
37
|
+
return crypto.createHash("sha1").update(value).digest("hex").slice(0, 16);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findRepoRoot(startDir) {
|
|
41
|
+
let current = path.resolve(startDir);
|
|
42
|
+
while (true) {
|
|
43
|
+
if (fs.existsSync(path.join(current, "packages", "extension", "package.json"))) {
|
|
44
|
+
return current;
|
|
45
|
+
}
|
|
46
|
+
const parent = path.dirname(current);
|
|
47
|
+
if (parent === current) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
current = parent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fingerprintFile(filePath) {
|
|
55
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stat = fs.statSync(filePath);
|
|
60
|
+
const hash = crypto.createHash("sha1").update(fs.readFileSync(filePath)).digest("hex");
|
|
61
|
+
return {
|
|
62
|
+
path: path.resolve(filePath),
|
|
63
|
+
hash,
|
|
64
|
+
shortHash: hash.slice(0, 12),
|
|
65
|
+
size: stat.size,
|
|
66
|
+
mtimeMs: stat.mtimeMs
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fingerprintsMatch(left, right) {
|
|
71
|
+
if (!left || !right) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return left.hash === right.hash && left.size === right.size;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function newestFileMtimeMs(filePath) {
|
|
79
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const stat = fs.statSync(filePath);
|
|
84
|
+
if (stat.isFile()) {
|
|
85
|
+
return stat.mtimeMs;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!stat.isDirectory()) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let newest = null;
|
|
93
|
+
for (const entry of fs.readdirSync(filePath, { withFileTypes: true })) {
|
|
94
|
+
const childNewest = newestFileMtimeMs(path.join(filePath, entry.name));
|
|
95
|
+
if (childNewest !== null && (newest === null || childNewest > newest)) {
|
|
96
|
+
newest = childNewest;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return newest;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getExtensionVsixFreshness(vsix) {
|
|
103
|
+
const resolvedVsix = path.resolve(vsix);
|
|
104
|
+
const knownDevVsixPaths = [defaultVsixPath, repoDefaultVsixPath].filter(Boolean);
|
|
105
|
+
if (!extensionPackageDir || !knownDevVsixPaths.some((knownPath) => normalizeForCompare(resolvedVsix) === normalizeForCompare(knownPath))) {
|
|
106
|
+
return {
|
|
107
|
+
supported: false,
|
|
108
|
+
reason: "non-default-vsix",
|
|
109
|
+
vsix: resolvedVsix
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const inputPaths = [
|
|
114
|
+
path.join(extensionPackageDir, "dist"),
|
|
115
|
+
path.join(extensionPackageDir, "extension.js"),
|
|
116
|
+
path.join(extensionPackageDir, "package.json"),
|
|
117
|
+
path.join(extensionPackageDir, "README.md"),
|
|
118
|
+
path.join(extensionPackageDir, ".vscodeignore")
|
|
119
|
+
];
|
|
120
|
+
const inputMtimes = inputPaths
|
|
121
|
+
.map((inputPath) => ({ path: inputPath, mtimeMs: newestFileMtimeMs(inputPath) }))
|
|
122
|
+
.filter((entry) => entry.mtimeMs !== null);
|
|
123
|
+
const newestInput = inputMtimes.reduce((best, entry) => {
|
|
124
|
+
if (!best || entry.mtimeMs > best.mtimeMs) {
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
return best;
|
|
128
|
+
}, null);
|
|
129
|
+
const vsixMtimeMs = fs.existsSync(resolvedVsix) ? fs.statSync(resolvedVsix).mtimeMs : null;
|
|
130
|
+
const stale = vsixMtimeMs === null || (newestInput !== null && newestInput.mtimeMs > vsixMtimeMs + 1000);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
supported: true,
|
|
134
|
+
stale,
|
|
135
|
+
vsix: resolvedVsix,
|
|
136
|
+
vsixMtimeMs,
|
|
137
|
+
newestInput
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function packageExtensionVsix() {
|
|
142
|
+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
143
|
+
return runCommand(npmCommand, ["--silent", "--prefix", "packages/extension", "run", "package:vsix"], {
|
|
144
|
+
cwd: repoRootDir ?? rootDir,
|
|
145
|
+
timeoutMs: 120_000
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildHostId(options) {
|
|
150
|
+
const hostKind = options.hostKind ?? "vscode-serve-web";
|
|
151
|
+
return `${hostKind}-${hashShort(normalizeForId(options.fixture ?? defaultFixtureDir))}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getHostDir(hostId) {
|
|
155
|
+
return path.join(managedRootDir, hostId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getMetadataPath(hostId) {
|
|
159
|
+
return path.join(getHostDir(hostId), "metadata.json");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getWatchdogPath(hostId) {
|
|
163
|
+
return path.join(getHostDir(hostId), "watchdog.json");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getLockDir(hostId) {
|
|
167
|
+
return path.join(getHostDir(hostId), "lock");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function ensureDir(dirPath) {
|
|
171
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function writeText(filePath, value) {
|
|
175
|
+
ensureDir(path.dirname(filePath));
|
|
176
|
+
fs.writeFileSync(filePath, value);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function appendText(filePath, value) {
|
|
180
|
+
ensureDir(path.dirname(filePath));
|
|
181
|
+
fs.appendFileSync(filePath, value);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function writeJsonAtomic(filePath, payload) {
|
|
185
|
+
ensureDir(path.dirname(filePath));
|
|
186
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
187
|
+
try {
|
|
188
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
189
|
+
fs.renameSync(tempPath, filePath);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
try {
|
|
192
|
+
fs.rmSync(tempPath, { force: true });
|
|
193
|
+
} catch {
|
|
194
|
+
// Best-effort cleanup only.
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function tail(text, maxLength = 4000) {
|
|
201
|
+
return text.length > maxLength ? text.slice(text.length - maxLength) : text;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function readTextIfExists(filePath) {
|
|
205
|
+
try {
|
|
206
|
+
return fs.readFileSync(filePath, "utf8");
|
|
207
|
+
} catch {
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function readRecentLogText(root, maxFiles = 12, maxLength = 4000) {
|
|
213
|
+
if (!fs.existsSync(root)) {
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
const files = [];
|
|
217
|
+
collectFiles(root, files, 5);
|
|
218
|
+
return files
|
|
219
|
+
.filter((filePath) => filePath.endsWith(".log") || filePath.endsWith(".txt"))
|
|
220
|
+
.sort((left, right) => {
|
|
221
|
+
const leftTime = fs.statSync(left).mtimeMs;
|
|
222
|
+
const rightTime = fs.statSync(right).mtimeMs;
|
|
223
|
+
return rightTime - leftTime;
|
|
224
|
+
})
|
|
225
|
+
.slice(0, maxFiles)
|
|
226
|
+
.map((filePath) => readTextIfExists(filePath))
|
|
227
|
+
.join("\n")
|
|
228
|
+
.slice(-maxLength);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function collectFiles(root, files, maxDepth) {
|
|
232
|
+
if (maxDepth < 0) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
let entries;
|
|
236
|
+
try {
|
|
237
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
238
|
+
} catch {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
for (const entry of entries) {
|
|
242
|
+
const entryPath = path.join(root, entry.name);
|
|
243
|
+
if (entry.isFile()) {
|
|
244
|
+
files.push(entryPath);
|
|
245
|
+
} else if (entry.isDirectory()) {
|
|
246
|
+
collectFiles(entryPath, files, maxDepth - 1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isProcessAlive(pid) {
|
|
252
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
process.kill(pid, 0);
|
|
258
|
+
return true;
|
|
259
|
+
} catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function workspaceMatchesFixture(entry, fixture) {
|
|
265
|
+
const expected = normalizeForCompare(fixture);
|
|
266
|
+
return (entry.workspaceFolders ?? []).some(
|
|
267
|
+
(workspaceFolder) => normalizeForCompare(workspaceFolder) === expected
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function resolveChromePath(explicitPath) {
|
|
272
|
+
const candidates = [
|
|
273
|
+
explicitPath,
|
|
274
|
+
findPackagedBrowserRuntime(),
|
|
275
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
276
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
277
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
278
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
279
|
+
"google-chrome",
|
|
280
|
+
"google-chrome-stable",
|
|
281
|
+
"chromium",
|
|
282
|
+
"chromium-browser",
|
|
283
|
+
"microsoft-edge"
|
|
284
|
+
].filter(Boolean);
|
|
285
|
+
|
|
286
|
+
return candidates.find((candidate) => commandOrFileExists(candidate)) ?? null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function findPackagedBrowserRuntime() {
|
|
290
|
+
const roots = [
|
|
291
|
+
path.join(rootDir, "runtimes", "browser"),
|
|
292
|
+
path.join(rootDir, "runtimes", "chrome"),
|
|
293
|
+
path.join(rootDir, "runtimes", "chromium")
|
|
294
|
+
];
|
|
295
|
+
const executableNames = process.platform === "win32"
|
|
296
|
+
? new Set(["chrome.exe", "chromium.exe", "msedge.exe"])
|
|
297
|
+
: new Set(["chrome", "chromium"]);
|
|
298
|
+
for (const runtimeRoot of roots) {
|
|
299
|
+
const found = findFirstExecutable(runtimeRoot, executableNames, 5);
|
|
300
|
+
if (found) {
|
|
301
|
+
return found;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function findFirstExecutable(root, executableNames, maxDepth) {
|
|
308
|
+
if (!fs.existsSync(root) || maxDepth < 0) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
let entries;
|
|
312
|
+
try {
|
|
313
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
const entryPath = path.join(root, entry.name);
|
|
319
|
+
if (entry.isFile() && executableNames.has(entry.name) && commandOrFileExists(entryPath)) {
|
|
320
|
+
return entryPath;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (const entry of entries) {
|
|
324
|
+
if (!entry.isDirectory()) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const found = findFirstExecutable(path.join(root, entry.name), executableNames, maxDepth - 1);
|
|
328
|
+
if (found) {
|
|
329
|
+
return found;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function commandOrFileExists(candidate) {
|
|
336
|
+
if (candidate.includes("\\") || candidate.includes("/") || /^[A-Za-z]:/.test(candidate)) {
|
|
337
|
+
return fs.existsSync(candidate);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const escaped = String(candidate).replace(/"/g, '\\"');
|
|
341
|
+
const result = process.platform === "win32"
|
|
342
|
+
? spawnSync("where.exe", [candidate], { encoding: "utf8", windowsHide: true, stdio: ["ignore", "pipe", "pipe"] })
|
|
343
|
+
: spawnSync("sh", ["-lc", `command -v "${escaped}"`], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
344
|
+
return result.status === 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function ideCommandEnv() {
|
|
348
|
+
return {
|
|
349
|
+
...process.env,
|
|
350
|
+
DONT_PROMPT_WSL_INSTALL: process.env.DONT_PROMPT_WSL_INSTALL ?? "1"
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function makeCommandSpec(command, prefixArgs = [], source = "command") {
|
|
355
|
+
return {
|
|
356
|
+
command,
|
|
357
|
+
prefixArgs,
|
|
358
|
+
source,
|
|
359
|
+
displayCommand: [command, ...prefixArgs].join(" ")
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function resolvePackagedNodeRuntime() {
|
|
364
|
+
const envNodePath = process.env.AIB_NODE_PATH;
|
|
365
|
+
if (envNodePath && commandOrFileExists(envNodePath)) {
|
|
366
|
+
return { command: path.resolve(envNodePath), source: "env-node" };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const platformDir = nodeRuntimePlatformDir();
|
|
370
|
+
if (!platformDir) {
|
|
371
|
+
return { command: process.execPath, source: "process-node" };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const candidateRoots = [
|
|
375
|
+
path.join(rootDir, "runtimes", "node", platformDir),
|
|
376
|
+
path.join(rootDir, "packages", "cli", "runtimes", "node", platformDir)
|
|
377
|
+
];
|
|
378
|
+
for (const candidateRoot of candidateRoots) {
|
|
379
|
+
const candidate = process.platform === "win32"
|
|
380
|
+
? path.join(candidateRoot, "node.exe")
|
|
381
|
+
: path.join(candidateRoot, "bin", "node");
|
|
382
|
+
if (fs.existsSync(candidate)) {
|
|
383
|
+
return { command: candidate, source: "packaged-node" };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return { command: process.execPath, source: "process-node" };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function nodeRuntimePlatformDir() {
|
|
391
|
+
const arch = process.arch;
|
|
392
|
+
if (process.platform === "win32" && arch === "x64") {
|
|
393
|
+
return "win-x64";
|
|
394
|
+
}
|
|
395
|
+
if (process.platform === "linux" && arch === "x64") {
|
|
396
|
+
return "linux-x64";
|
|
397
|
+
}
|
|
398
|
+
if (process.platform === "linux" && arch === "arm64") {
|
|
399
|
+
return "linux-arm64";
|
|
400
|
+
}
|
|
401
|
+
if (process.platform === "darwin" && arch === "x64") {
|
|
402
|
+
return "darwin-x64";
|
|
403
|
+
}
|
|
404
|
+
if (process.platform === "darwin" && arch === "arm64") {
|
|
405
|
+
return "darwin-arm64";
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function commandDisplayName(commandSpec) {
|
|
411
|
+
return typeof commandSpec === "string" ? commandSpec : commandSpec.displayCommand;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function commandExecutable(commandSpec) {
|
|
415
|
+
return typeof commandSpec === "string" ? commandSpec : commandSpec.command;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function commandArgs(commandSpec, args) {
|
|
419
|
+
return typeof commandSpec === "string" ? args : [...commandSpec.prefixArgs, ...args];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function runCommand(commandSpec, args, options = {}) {
|
|
423
|
+
return run(commandExecutable(commandSpec), commandArgs(commandSpec, args), options);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function startLoggedCommand(commandSpec, args, options) {
|
|
427
|
+
return startLoggedProcess(commandExecutable(commandSpec), commandArgs(commandSpec, args), options);
|
|
428
|
+
}
|
|
429
|
+
|
|
342
430
|
function resolvePackagedCodeServerCommand() {
|
|
343
431
|
const nodeRuntime = resolvePackagedNodeRuntime();
|
|
344
432
|
const candidates = [
|
|
@@ -356,8 +444,8 @@ function resolvePackagedCodeServerCommand() {
|
|
|
356
444
|
return makeCommandSpec(nodeRuntime.command, [entryPath], nodeRuntime.source === "packaged-node"
|
|
357
445
|
? "packaged-code-server-entry-bundled-node"
|
|
358
446
|
: "packaged-code-server-entry");
|
|
359
|
-
}
|
|
360
|
-
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
361
449
|
return null;
|
|
362
450
|
}
|
|
363
451
|
|
|
@@ -384,249 +472,249 @@ function managedRuntimePlatformStatus(packageRoot) {
|
|
|
384
472
|
return { ok: false, reason: "invalid", expectedPlatformKey, actualPlatformKey: null };
|
|
385
473
|
}
|
|
386
474
|
}
|
|
387
|
-
|
|
388
|
-
function resolveManagedHostKind(options, commandSpec) {
|
|
389
|
-
const raw = options.hostKind ?? process.env.AIB_MANAGED_HOST_KIND ?? "";
|
|
390
|
-
if (raw) {
|
|
391
|
-
if (raw !== "vscode-serve-web" && raw !== "code-server") {
|
|
392
|
-
throw new Error("Expected managed host kind to be vscode-serve-web or code-server.");
|
|
393
|
-
}
|
|
394
|
-
return raw;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const command = commandDisplayName(commandSpec);
|
|
398
|
-
const commandName = path.basename(String(command)).toLowerCase();
|
|
399
|
-
return commandName === "code-server"
|
|
400
|
-
|| commandName === "code-server.cmd"
|
|
401
|
-
|| command.endsWith(path.join("code-server", "out", "node", "entry.js"))
|
|
402
|
-
? "code-server"
|
|
403
|
-
: "vscode-serve-web";
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function resolveManagedIdeCommand(options) {
|
|
407
|
-
if (options.ideCommand) {
|
|
408
|
-
return makeCommandSpec(options.ideCommand, [], "explicit");
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const rawHostKind = options.hostKind ?? process.env.AIB_MANAGED_HOST_KIND ?? "";
|
|
412
|
-
if (rawHostKind === "code-server") {
|
|
413
|
-
if (process.env.AIB_CODE_SERVER_PATH && commandOrFileExists(process.env.AIB_CODE_SERVER_PATH)) {
|
|
414
|
-
return makeCommandSpec(process.env.AIB_CODE_SERVER_PATH, [], "env-code-server");
|
|
415
|
-
}
|
|
416
|
-
const packagedCommand = resolvePackagedCodeServerCommand();
|
|
417
|
-
if (packagedCommand) {
|
|
418
|
-
return packagedCommand;
|
|
419
|
-
}
|
|
420
|
-
const candidates = [
|
|
421
|
-
path.join(rootDir, "runtimes", "code-server", "bin", process.platform === "win32" ? "code-server.cmd" : "code-server"),
|
|
422
|
-
path.join(rootDir, "runtimes", "code-server", "bin", "code-server"),
|
|
423
|
-
path.join(rootDir, "packages", "cli", "runtimes", "code-server", "bin", process.platform === "win32" ? "code-server.cmd" : "code-server"),
|
|
424
|
-
path.join(rootDir, "packages", "cli", "runtimes", "code-server", "bin", "code-server"),
|
|
425
|
-
"code-server"
|
|
426
|
-
].filter(Boolean);
|
|
427
|
-
const found = candidates.find((candidate) => commandOrFileExists(candidate)) ?? "code-server";
|
|
428
|
-
return makeCommandSpec(found, [], "code-server-path");
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
return makeCommandSpec(resolveIdeCommand({ ide: "code" }), [], "vscode-code");
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function buildIdeCommandEnv(runDir) {
|
|
435
|
-
const env = ideCommandEnv();
|
|
436
|
-
if (process.platform !== "win32") {
|
|
437
|
-
const homeDir = path.join(runDir, "home");
|
|
438
|
-
env.HOME = homeDir;
|
|
439
|
-
env.VSCODE_CLI_DATA_DIR = path.join(runDir, "cli-data");
|
|
440
|
-
}
|
|
441
|
-
return env;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function readJsonFile(filePath) {
|
|
445
|
-
try {
|
|
446
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
447
|
-
} catch {
|
|
448
|
-
return null;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function readMetadataByHostId(hostId) {
|
|
453
|
-
const metadataPath = getMetadataPath(hostId);
|
|
454
|
-
const metadata = fs.existsSync(metadataPath) ? readJsonFile(metadataPath) : null;
|
|
455
|
-
if (!metadata || typeof metadata !== "object") {
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
return metadata;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function readAllManagedHostEntries() {
|
|
462
|
-
if (!fs.existsSync(managedRootDir)) {
|
|
463
|
-
return [];
|
|
464
|
-
}
|
|
465
|
-
const entries = [];
|
|
466
|
-
for (const item of fs.readdirSync(managedRootDir, { withFileTypes: true })) {
|
|
467
|
-
if (!item.isDirectory()) {
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
const metadata = readMetadataByHostId(item.name);
|
|
471
|
-
if (!metadata) {
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
entries.push({
|
|
475
|
-
hostId: item.name,
|
|
476
|
-
metadata
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
return entries.sort((left, right) => String(left.hostId).localeCompare(String(right.hostId)));
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function compactRuntimeStatus(status, metadata = {}) {
|
|
483
|
-
return {
|
|
484
|
-
hostId: status.hostId ?? metadata.hostId ?? null,
|
|
485
|
-
hostKind: status.hostKind ?? metadata.hostKind ?? null,
|
|
486
|
-
running: status.running === true,
|
|
487
|
-
healthy: status.healthy === true,
|
|
488
|
-
fixture: metadata.fixture ?? status.metadata?.fixture ?? null,
|
|
489
|
-
runDir: metadata.runDir ?? status.metadata?.runDir ?? null,
|
|
490
|
-
rootUrl: metadata.rootUrl ?? status.metadata?.rootUrl ?? null,
|
|
491
|
-
reason: status.reason ?? null
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function writeMetadata(hostId, metadata) {
|
|
496
|
-
writeJsonAtomic(getMetadataPath(hostId), metadata);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function removeMetadata(hostId) {
|
|
500
|
-
fs.rmSync(getMetadataPath(hostId), { force: true });
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function quoteCmdArg(value) {
|
|
504
|
-
const raw = String(value);
|
|
505
|
-
return `"${raw.replace(/%/g, "%%").replace(/"/g, '\\"')}"`;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function quotePowerShellString(value) {
|
|
509
|
-
return `'${String(value).replace(/'/g, "''")}'`;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function writeWindowsDetachedScript(command, args, options) {
|
|
513
|
-
ensureDir(path.dirname(options.stdoutPath));
|
|
514
|
-
ensureDir(path.dirname(options.stderrPath));
|
|
515
|
-
const scriptPath = `${options.stdoutPath}.cmd`;
|
|
516
|
-
const cwd = options.cwd ?? rootDir;
|
|
517
|
-
const commandLine = [
|
|
518
|
-
quoteCmdArg(command),
|
|
519
|
-
...args.map(quoteCmdArg),
|
|
520
|
-
">",
|
|
521
|
-
quoteCmdArg(options.stdoutPath),
|
|
522
|
-
"2>",
|
|
523
|
-
quoteCmdArg(options.stderrPath)
|
|
524
|
-
].join(" ");
|
|
525
|
-
writeText(scriptPath, [
|
|
526
|
-
"@echo off",
|
|
527
|
-
`cd /d ${quoteCmdArg(cwd)}`,
|
|
528
|
-
commandLine,
|
|
529
|
-
""
|
|
530
|
-
].join("\r\n"));
|
|
531
|
-
return scriptPath;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function startWindowsHiddenScript(scriptPath, options) {
|
|
535
|
-
const cwd = options.cwd ?? rootDir;
|
|
536
|
-
const command = [
|
|
537
|
-
"$p = Start-Process -FilePath 'cmd.exe'",
|
|
538
|
-
`-ArgumentList @('/d','/s','/c', ${quotePowerShellString(scriptPath)})`,
|
|
539
|
-
`-WorkingDirectory ${quotePowerShellString(cwd)}`,
|
|
540
|
-
"-WindowStyle Hidden -PassThru;",
|
|
541
|
-
"$p.Id"
|
|
542
|
-
].join(" ");
|
|
543
|
-
const result = spawnSync("powershell.exe", [
|
|
544
|
-
"-NoProfile",
|
|
545
|
-
"-ExecutionPolicy",
|
|
546
|
-
"Bypass",
|
|
547
|
-
"-WindowStyle",
|
|
548
|
-
"Hidden",
|
|
549
|
-
"-Command",
|
|
550
|
-
command
|
|
551
|
-
], {
|
|
552
|
-
cwd,
|
|
553
|
-
env: options.env ?? process.env,
|
|
554
|
-
encoding: "utf8",
|
|
555
|
-
windowsHide: true,
|
|
556
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
557
|
-
});
|
|
558
|
-
if (result.status !== 0) {
|
|
559
|
-
appendText(options.stderrPath, `${result.stdout ?? ""}${result.stderr ?? ""}${result.error ? result.error.message : ""}\n`);
|
|
560
|
-
}
|
|
561
|
-
const pid = Number(String(result.stdout ?? "").trim().split(/\r?\n/).pop());
|
|
562
|
-
return {
|
|
563
|
-
pid: Number.isFinite(pid) && pid > 0 ? pid : null,
|
|
564
|
-
on() {},
|
|
565
|
-
unref() {}
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function startLoggedProcess(command, args, options) {
|
|
570
|
-
const spawnCommand = prepareSpawnCommand(command, args);
|
|
571
|
-
if (options.detached === true && process.platform === "win32") {
|
|
572
|
-
const scriptPath = writeWindowsDetachedScript(spawnCommand.command, spawnCommand.args, options);
|
|
573
|
-
const child = startWindowsHiddenScript(scriptPath, options);
|
|
574
|
-
return {
|
|
575
|
-
child,
|
|
576
|
-
command,
|
|
577
|
-
args,
|
|
578
|
-
stdoutPath: options.stdoutPath,
|
|
579
|
-
stderrPath: options.stderrPath
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const stdoutFd = fs.openSync(options.stdoutPath, "a");
|
|
584
|
-
const stderrFd = fs.openSync(options.stderrPath, "a");
|
|
585
|
-
const child = spawn(spawnCommand.command, spawnCommand.args, {
|
|
586
|
-
cwd: options.cwd ?? rootDir,
|
|
587
|
-
env: options.env ?? process.env,
|
|
588
|
-
detached: options.detached === true,
|
|
589
|
-
windowsHide: true,
|
|
590
|
-
windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments ?? false,
|
|
591
|
-
stdio: ["ignore", stdoutFd, stderrFd]
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
child.on("error", (error) => {
|
|
595
|
-
appendText(options.stderrPath, `${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
596
|
-
});
|
|
597
|
-
if (options.detached === true) {
|
|
598
|
-
child.unref();
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
return {
|
|
602
|
-
child,
|
|
603
|
-
command,
|
|
604
|
-
args,
|
|
605
|
-
stdoutPath: options.stdoutPath,
|
|
606
|
-
stderrPath: options.stderrPath
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function stopProcessTree(pid) {
|
|
611
|
-
if (!Number.isInteger(pid) || pid <= 0) {
|
|
612
|
-
return {
|
|
613
|
-
attempted: false,
|
|
614
|
-
pid
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
if (process.platform === "win32") {
|
|
619
|
-
const result = run("taskkill.exe", ["/pid", String(pid), "/t", "/f"], { timeoutMs: 10_000 });
|
|
620
|
-
return {
|
|
621
|
-
attempted: true,
|
|
622
|
-
pid,
|
|
623
|
-
status: result.status,
|
|
624
|
-
error: result.error,
|
|
625
|
-
stdoutTail: tail(result.stdout, 1000),
|
|
626
|
-
stderrTail: tail(result.stderr, 1000)
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
475
|
+
|
|
476
|
+
function resolveManagedHostKind(options, commandSpec) {
|
|
477
|
+
const raw = options.hostKind ?? process.env.AIB_MANAGED_HOST_KIND ?? "";
|
|
478
|
+
if (raw) {
|
|
479
|
+
if (raw !== "vscode-serve-web" && raw !== "code-server") {
|
|
480
|
+
throw new Error("Expected managed host kind to be vscode-serve-web or code-server.");
|
|
481
|
+
}
|
|
482
|
+
return raw;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const command = commandDisplayName(commandSpec);
|
|
486
|
+
const commandName = path.basename(String(command)).toLowerCase();
|
|
487
|
+
return commandName === "code-server"
|
|
488
|
+
|| commandName === "code-server.cmd"
|
|
489
|
+
|| command.endsWith(path.join("code-server", "out", "node", "entry.js"))
|
|
490
|
+
? "code-server"
|
|
491
|
+
: "vscode-serve-web";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function resolveManagedIdeCommand(options) {
|
|
495
|
+
if (options.ideCommand) {
|
|
496
|
+
return makeCommandSpec(options.ideCommand, [], "explicit");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const rawHostKind = options.hostKind ?? process.env.AIB_MANAGED_HOST_KIND ?? "";
|
|
500
|
+
if (rawHostKind === "code-server") {
|
|
501
|
+
if (process.env.AIB_CODE_SERVER_PATH && commandOrFileExists(process.env.AIB_CODE_SERVER_PATH)) {
|
|
502
|
+
return makeCommandSpec(process.env.AIB_CODE_SERVER_PATH, [], "env-code-server");
|
|
503
|
+
}
|
|
504
|
+
const packagedCommand = resolvePackagedCodeServerCommand();
|
|
505
|
+
if (packagedCommand) {
|
|
506
|
+
return packagedCommand;
|
|
507
|
+
}
|
|
508
|
+
const candidates = [
|
|
509
|
+
path.join(rootDir, "runtimes", "code-server", "bin", process.platform === "win32" ? "code-server.cmd" : "code-server"),
|
|
510
|
+
path.join(rootDir, "runtimes", "code-server", "bin", "code-server"),
|
|
511
|
+
path.join(rootDir, "packages", "cli", "runtimes", "code-server", "bin", process.platform === "win32" ? "code-server.cmd" : "code-server"),
|
|
512
|
+
path.join(rootDir, "packages", "cli", "runtimes", "code-server", "bin", "code-server"),
|
|
513
|
+
"code-server"
|
|
514
|
+
].filter(Boolean);
|
|
515
|
+
const found = candidates.find((candidate) => commandOrFileExists(candidate)) ?? "code-server";
|
|
516
|
+
return makeCommandSpec(found, [], "code-server-path");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return makeCommandSpec(resolveIdeCommand({ ide: "code" }), [], "vscode-code");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function buildIdeCommandEnv(runDir) {
|
|
523
|
+
const env = ideCommandEnv();
|
|
524
|
+
if (process.platform !== "win32") {
|
|
525
|
+
const homeDir = path.join(runDir, "home");
|
|
526
|
+
env.HOME = homeDir;
|
|
527
|
+
env.VSCODE_CLI_DATA_DIR = path.join(runDir, "cli-data");
|
|
528
|
+
}
|
|
529
|
+
return env;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function readJsonFile(filePath) {
|
|
533
|
+
try {
|
|
534
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
535
|
+
} catch {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function readMetadataByHostId(hostId) {
|
|
541
|
+
const metadataPath = getMetadataPath(hostId);
|
|
542
|
+
const metadata = fs.existsSync(metadataPath) ? readJsonFile(metadataPath) : null;
|
|
543
|
+
if (!metadata || typeof metadata !== "object") {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
return metadata;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function readAllManagedHostEntries() {
|
|
550
|
+
if (!fs.existsSync(managedRootDir)) {
|
|
551
|
+
return [];
|
|
552
|
+
}
|
|
553
|
+
const entries = [];
|
|
554
|
+
for (const item of fs.readdirSync(managedRootDir, { withFileTypes: true })) {
|
|
555
|
+
if (!item.isDirectory()) {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
const metadata = readMetadataByHostId(item.name);
|
|
559
|
+
if (!metadata) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
entries.push({
|
|
563
|
+
hostId: item.name,
|
|
564
|
+
metadata
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
return entries.sort((left, right) => String(left.hostId).localeCompare(String(right.hostId)));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function compactRuntimeStatus(status, metadata = {}) {
|
|
571
|
+
return {
|
|
572
|
+
hostId: status.hostId ?? metadata.hostId ?? null,
|
|
573
|
+
hostKind: status.hostKind ?? metadata.hostKind ?? null,
|
|
574
|
+
running: status.running === true,
|
|
575
|
+
healthy: status.healthy === true,
|
|
576
|
+
fixture: metadata.fixture ?? status.metadata?.fixture ?? null,
|
|
577
|
+
runDir: metadata.runDir ?? status.metadata?.runDir ?? null,
|
|
578
|
+
rootUrl: metadata.rootUrl ?? status.metadata?.rootUrl ?? null,
|
|
579
|
+
reason: status.reason ?? null
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function writeMetadata(hostId, metadata) {
|
|
584
|
+
writeJsonAtomic(getMetadataPath(hostId), metadata);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function removeMetadata(hostId) {
|
|
588
|
+
fs.rmSync(getMetadataPath(hostId), { force: true });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function quoteCmdArg(value) {
|
|
592
|
+
const raw = String(value);
|
|
593
|
+
return `"${raw.replace(/%/g, "%%").replace(/"/g, '\\"')}"`;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function quotePowerShellString(value) {
|
|
597
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function writeWindowsDetachedScript(command, args, options) {
|
|
601
|
+
ensureDir(path.dirname(options.stdoutPath));
|
|
602
|
+
ensureDir(path.dirname(options.stderrPath));
|
|
603
|
+
const scriptPath = `${options.stdoutPath}.cmd`;
|
|
604
|
+
const cwd = options.cwd ?? rootDir;
|
|
605
|
+
const commandLine = [
|
|
606
|
+
quoteCmdArg(command),
|
|
607
|
+
...args.map(quoteCmdArg),
|
|
608
|
+
">",
|
|
609
|
+
quoteCmdArg(options.stdoutPath),
|
|
610
|
+
"2>",
|
|
611
|
+
quoteCmdArg(options.stderrPath)
|
|
612
|
+
].join(" ");
|
|
613
|
+
writeText(scriptPath, [
|
|
614
|
+
"@echo off",
|
|
615
|
+
`cd /d ${quoteCmdArg(cwd)}`,
|
|
616
|
+
commandLine,
|
|
617
|
+
""
|
|
618
|
+
].join("\r\n"));
|
|
619
|
+
return scriptPath;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function startWindowsHiddenScript(scriptPath, options) {
|
|
623
|
+
const cwd = options.cwd ?? rootDir;
|
|
624
|
+
const command = [
|
|
625
|
+
"$p = Start-Process -FilePath 'cmd.exe'",
|
|
626
|
+
`-ArgumentList @('/d','/s','/c', ${quotePowerShellString(scriptPath)})`,
|
|
627
|
+
`-WorkingDirectory ${quotePowerShellString(cwd)}`,
|
|
628
|
+
"-WindowStyle Hidden -PassThru;",
|
|
629
|
+
"$p.Id"
|
|
630
|
+
].join(" ");
|
|
631
|
+
const result = spawnSync("powershell.exe", [
|
|
632
|
+
"-NoProfile",
|
|
633
|
+
"-ExecutionPolicy",
|
|
634
|
+
"Bypass",
|
|
635
|
+
"-WindowStyle",
|
|
636
|
+
"Hidden",
|
|
637
|
+
"-Command",
|
|
638
|
+
command
|
|
639
|
+
], {
|
|
640
|
+
cwd,
|
|
641
|
+
env: options.env ?? process.env,
|
|
642
|
+
encoding: "utf8",
|
|
643
|
+
windowsHide: true,
|
|
644
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
645
|
+
});
|
|
646
|
+
if (result.status !== 0) {
|
|
647
|
+
appendText(options.stderrPath, `${result.stdout ?? ""}${result.stderr ?? ""}${result.error ? result.error.message : ""}\n`);
|
|
648
|
+
}
|
|
649
|
+
const pid = Number(String(result.stdout ?? "").trim().split(/\r?\n/).pop());
|
|
650
|
+
return {
|
|
651
|
+
pid: Number.isFinite(pid) && pid > 0 ? pid : null,
|
|
652
|
+
on() {},
|
|
653
|
+
unref() {}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function startLoggedProcess(command, args, options) {
|
|
658
|
+
const spawnCommand = prepareSpawnCommand(command, args);
|
|
659
|
+
if (options.detached === true && process.platform === "win32") {
|
|
660
|
+
const scriptPath = writeWindowsDetachedScript(spawnCommand.command, spawnCommand.args, options);
|
|
661
|
+
const child = startWindowsHiddenScript(scriptPath, options);
|
|
662
|
+
return {
|
|
663
|
+
child,
|
|
664
|
+
command,
|
|
665
|
+
args,
|
|
666
|
+
stdoutPath: options.stdoutPath,
|
|
667
|
+
stderrPath: options.stderrPath
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const stdoutFd = fs.openSync(options.stdoutPath, "a");
|
|
672
|
+
const stderrFd = fs.openSync(options.stderrPath, "a");
|
|
673
|
+
const child = spawn(spawnCommand.command, spawnCommand.args, {
|
|
674
|
+
cwd: options.cwd ?? rootDir,
|
|
675
|
+
env: options.env ?? process.env,
|
|
676
|
+
detached: options.detached === true,
|
|
677
|
+
windowsHide: true,
|
|
678
|
+
windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments ?? false,
|
|
679
|
+
stdio: ["ignore", stdoutFd, stderrFd]
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
child.on("error", (error) => {
|
|
683
|
+
appendText(options.stderrPath, `${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
684
|
+
});
|
|
685
|
+
if (options.detached === true) {
|
|
686
|
+
child.unref();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return {
|
|
690
|
+
child,
|
|
691
|
+
command,
|
|
692
|
+
args,
|
|
693
|
+
stdoutPath: options.stdoutPath,
|
|
694
|
+
stderrPath: options.stderrPath
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function stopProcessTree(pid) {
|
|
699
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
700
|
+
return {
|
|
701
|
+
attempted: false,
|
|
702
|
+
pid
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (process.platform === "win32") {
|
|
707
|
+
const result = run("taskkill.exe", ["/pid", String(pid), "/t", "/f"], { timeoutMs: 10_000 });
|
|
708
|
+
return {
|
|
709
|
+
attempted: true,
|
|
710
|
+
pid,
|
|
711
|
+
status: result.status,
|
|
712
|
+
error: result.error,
|
|
713
|
+
stdoutTail: tail(result.stdout, 1000),
|
|
714
|
+
stderrTail: tail(result.stderr, 1000)
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
630
718
|
try {
|
|
631
719
|
if (process.platform !== "win32") {
|
|
632
720
|
process.kill(-pid, "SIGTERM");
|
|
@@ -648,12 +736,12 @@ function stopProcessTree(pid) {
|
|
|
648
736
|
return {
|
|
649
737
|
attempted: true,
|
|
650
738
|
pid,
|
|
651
|
-
status: 0,
|
|
652
|
-
error: null
|
|
653
|
-
};
|
|
654
|
-
} catch (error) {
|
|
655
|
-
if (process.platform !== "win32") {
|
|
656
|
-
try {
|
|
739
|
+
status: 0,
|
|
740
|
+
error: null
|
|
741
|
+
};
|
|
742
|
+
} catch (error) {
|
|
743
|
+
if (process.platform !== "win32") {
|
|
744
|
+
try {
|
|
657
745
|
process.kill(pid, "SIGTERM");
|
|
658
746
|
if (!waitForProcessExit(pid, 2000)) {
|
|
659
747
|
try {
|
|
@@ -666,26 +754,26 @@ function stopProcessTree(pid) {
|
|
|
666
754
|
return {
|
|
667
755
|
attempted: true,
|
|
668
756
|
pid,
|
|
669
|
-
status: 0,
|
|
670
|
-
error: null
|
|
671
|
-
};
|
|
672
|
-
} catch {
|
|
673
|
-
// Keep the original process-group error for diagnostics.
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
return {
|
|
677
|
-
attempted: true,
|
|
678
|
-
pid,
|
|
679
|
-
status: 1,
|
|
680
|
-
error: error instanceof Error ? error.message : String(error)
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
function stopProcessesByCommandLineFragment(fragment) {
|
|
686
|
-
if (!fragment) {
|
|
757
|
+
status: 0,
|
|
758
|
+
error: null
|
|
759
|
+
};
|
|
760
|
+
} catch {
|
|
761
|
+
// Keep the original process-group error for diagnostics.
|
|
762
|
+
}
|
|
763
|
+
}
|
|
687
764
|
return {
|
|
688
|
-
attempted:
|
|
765
|
+
attempted: true,
|
|
766
|
+
pid,
|
|
767
|
+
status: 1,
|
|
768
|
+
error: error instanceof Error ? error.message : String(error)
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function stopProcessesByCommandLineFragment(fragment) {
|
|
774
|
+
if (!fragment) {
|
|
775
|
+
return {
|
|
776
|
+
attempted: false,
|
|
689
777
|
fragment
|
|
690
778
|
};
|
|
691
779
|
}
|
|
@@ -713,7 +801,7 @@ function stopProcessesByCommandLineFragment(fragment) {
|
|
|
713
801
|
status: result.status,
|
|
714
802
|
error: result.error,
|
|
715
803
|
stdoutTail: tail(result.stdout, 1000),
|
|
716
|
-
stderrTail: tail(result.stderr, 1000)
|
|
804
|
+
stderrTail: tail(result.stderr, 1000)
|
|
717
805
|
};
|
|
718
806
|
}
|
|
719
807
|
|
|
@@ -784,79 +872,79 @@ function stopProcessesByCommandLineFragmentUnix(fragment) {
|
|
|
784
872
|
}
|
|
785
873
|
|
|
786
874
|
function stopOrphanedPackagedRuntimeProcesses(activeRunDirs = []) {
|
|
787
|
-
if (process.platform !== "win32") {
|
|
788
|
-
return {
|
|
789
|
-
attempted: false,
|
|
790
|
-
reason: "unsupported-platform"
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const escapedRoot = rootDir.replace(/'/g, "''");
|
|
795
|
-
const activeList = activeRunDirs
|
|
796
|
-
.filter(Boolean)
|
|
797
|
-
.map((item) => "'" + String(item).replace(/'/g, "''") + "'")
|
|
798
|
-
.join(",");
|
|
799
|
-
const script = [
|
|
800
|
-
"$root = '" + escapedRoot + "'",
|
|
801
|
-
"$active = @(" + activeList + ")",
|
|
802
|
-
"$runtimePattern = [regex]::Escape($root + '\\runtimes\\')",
|
|
803
|
-
"$runPattern = [regex]::Escape($root + '\\.tmp\\managed-host-runs\\')",
|
|
804
|
-
"Get-CimInstance Win32_Process |",
|
|
805
|
-
"Where-Object {",
|
|
806
|
-
" $cmd = $_.CommandLine",
|
|
807
|
-
" $cmd -and",
|
|
808
|
-
" $cmd -match $runtimePattern -and",
|
|
809
|
-
" $cmd -match $runPattern -and",
|
|
810
|
-
" -not ($active | Where-Object { $_ -and $cmd.Contains($_) })",
|
|
811
|
-
"} |",
|
|
812
|
-
"ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop; $_.ProcessId } catch {} }"
|
|
813
|
-
].join(" ");
|
|
814
|
-
const result = run("powershell.exe", ["-NoProfile", "-Command", script], { timeoutMs: 10_000 });
|
|
815
|
-
return {
|
|
816
|
-
attempted: true,
|
|
817
|
-
status: result.status,
|
|
818
|
-
error: result.error,
|
|
819
|
-
stdoutTail: tail(result.stdout, 1000),
|
|
820
|
-
stderrTail: tail(result.stderr, 1000)
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
|
|
875
|
+
if (process.platform !== "win32") {
|
|
876
|
+
return {
|
|
877
|
+
attempted: false,
|
|
878
|
+
reason: "unsupported-platform"
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const escapedRoot = rootDir.replace(/'/g, "''");
|
|
883
|
+
const activeList = activeRunDirs
|
|
884
|
+
.filter(Boolean)
|
|
885
|
+
.map((item) => "'" + String(item).replace(/'/g, "''") + "'")
|
|
886
|
+
.join(",");
|
|
887
|
+
const script = [
|
|
888
|
+
"$root = '" + escapedRoot + "'",
|
|
889
|
+
"$active = @(" + activeList + ")",
|
|
890
|
+
"$runtimePattern = [regex]::Escape($root + '\\runtimes\\')",
|
|
891
|
+
"$runPattern = [regex]::Escape($root + '\\.tmp\\managed-host-runs\\')",
|
|
892
|
+
"Get-CimInstance Win32_Process |",
|
|
893
|
+
"Where-Object {",
|
|
894
|
+
" $cmd = $_.CommandLine",
|
|
895
|
+
" $cmd -and",
|
|
896
|
+
" $cmd -match $runtimePattern -and",
|
|
897
|
+
" $cmd -match $runPattern -and",
|
|
898
|
+
" -not ($active | Where-Object { $_ -and $cmd.Contains($_) })",
|
|
899
|
+
"} |",
|
|
900
|
+
"ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop; $_.ProcessId } catch {} }"
|
|
901
|
+
].join(" ");
|
|
902
|
+
const result = run("powershell.exe", ["-NoProfile", "-Command", script], { timeoutMs: 10_000 });
|
|
903
|
+
return {
|
|
904
|
+
attempted: true,
|
|
905
|
+
status: result.status,
|
|
906
|
+
error: result.error,
|
|
907
|
+
stdoutTail: tail(result.stdout, 1000),
|
|
908
|
+
stderrTail: tail(result.stderr, 1000)
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
824
912
|
function stopAibOwnedRuntimeProcesses() {
|
|
825
913
|
if (process.platform !== "win32") {
|
|
826
914
|
return stopAibOwnedRuntimeProcessesUnix();
|
|
827
915
|
}
|
|
828
|
-
|
|
829
|
-
const roots = [
|
|
830
|
-
rootDir,
|
|
831
|
-
path.join(rootDir, "packages", "cli")
|
|
832
|
-
].map((item) => String(item).replace(/'/g, "''"));
|
|
833
|
-
const script = [
|
|
834
|
-
"$roots = @(" + roots.map((item) => "'" + item + "'").join(",") + ")",
|
|
835
|
-
"$processes = @(Get-CimInstance Win32_Process | Where-Object {",
|
|
836
|
-
" $cmd = $_.CommandLine",
|
|
837
|
-
" if (-not $cmd) { return $false }",
|
|
838
|
-
" $underAibRoot = [bool]($roots | Where-Object { $_ -and $cmd.Contains($_) })",
|
|
839
|
-
" $hasRunMarker = $cmd.Contains('managed-host-runs')",
|
|
840
|
-
" $isAibWatchdog = $cmd.Contains('manage-serve-web-host.cjs') -and $cmd.Contains('watchdog') -and $underAibRoot",
|
|
841
|
-
" $isPackagedRuntime = $underAibRoot -and ($cmd.Contains('\\runtimes\\code-server\\') -or $cmd.Contains('\\runtimes\\browser\\'))",
|
|
842
|
-
" $isAibDetachedWrapper = $underAibRoot -and ($cmd.Contains('serve-web.stdout.txt.cmd') -or $cmd.Contains('headless-browser.stdout.txt.cmd') -or $cmd.Contains('watchdog.stdout.txt.cmd'))",
|
|
843
|
-
" ($hasRunMarker -and $underAibRoot) -or $isAibWatchdog -or $isPackagedRuntime -or $isAibDetachedWrapper",
|
|
844
|
-
"})",
|
|
845
|
-
"$processes | ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop; $_.ProcessId } catch {} }"
|
|
846
|
-
].join("\n");
|
|
847
|
-
const result = run("powershell.exe", ["-NoProfile", "-Command", script], { timeoutMs: 15_000 });
|
|
848
|
-
const killed = result.stdout
|
|
849
|
-
.split(/\r?\n/)
|
|
850
|
-
.map((line) => line.trim())
|
|
851
|
-
.filter(Boolean).length;
|
|
852
|
-
return {
|
|
853
|
-
attempted: true,
|
|
854
|
-
killed,
|
|
855
|
-
status: result.status,
|
|
856
|
-
error: result.error,
|
|
857
|
-
stdoutTail: tail(result.stdout, 1000),
|
|
858
|
-
stderrTail: tail(result.stderr, 1000)
|
|
859
|
-
};
|
|
916
|
+
|
|
917
|
+
const roots = [
|
|
918
|
+
rootDir,
|
|
919
|
+
path.join(rootDir, "packages", "cli")
|
|
920
|
+
].map((item) => String(item).replace(/'/g, "''"));
|
|
921
|
+
const script = [
|
|
922
|
+
"$roots = @(" + roots.map((item) => "'" + item + "'").join(",") + ")",
|
|
923
|
+
"$processes = @(Get-CimInstance Win32_Process | Where-Object {",
|
|
924
|
+
" $cmd = $_.CommandLine",
|
|
925
|
+
" if (-not $cmd) { return $false }",
|
|
926
|
+
" $underAibRoot = [bool]($roots | Where-Object { $_ -and $cmd.Contains($_) })",
|
|
927
|
+
" $hasRunMarker = $cmd.Contains('managed-host-runs')",
|
|
928
|
+
" $isAibWatchdog = $cmd.Contains('manage-serve-web-host.cjs') -and $cmd.Contains('watchdog') -and $underAibRoot",
|
|
929
|
+
" $isPackagedRuntime = $underAibRoot -and ($cmd.Contains('\\runtimes\\code-server\\') -or $cmd.Contains('\\runtimes\\browser\\'))",
|
|
930
|
+
" $isAibDetachedWrapper = $underAibRoot -and ($cmd.Contains('serve-web.stdout.txt.cmd') -or $cmd.Contains('headless-browser.stdout.txt.cmd') -or $cmd.Contains('watchdog.stdout.txt.cmd'))",
|
|
931
|
+
" ($hasRunMarker -and $underAibRoot) -or $isAibWatchdog -or $isPackagedRuntime -or $isAibDetachedWrapper",
|
|
932
|
+
"})",
|
|
933
|
+
"$processes | ForEach-Object { try { Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop; $_.ProcessId } catch {} }"
|
|
934
|
+
].join("\n");
|
|
935
|
+
const result = run("powershell.exe", ["-NoProfile", "-Command", script], { timeoutMs: 15_000 });
|
|
936
|
+
const killed = result.stdout
|
|
937
|
+
.split(/\r?\n/)
|
|
938
|
+
.map((line) => line.trim())
|
|
939
|
+
.filter(Boolean).length;
|
|
940
|
+
return {
|
|
941
|
+
attempted: true,
|
|
942
|
+
killed,
|
|
943
|
+
status: result.status,
|
|
944
|
+
error: result.error,
|
|
945
|
+
stdoutTail: tail(result.stdout, 1000),
|
|
946
|
+
stderrTail: tail(result.stderr, 1000)
|
|
947
|
+
};
|
|
860
948
|
}
|
|
861
949
|
|
|
862
950
|
function stopAibOwnedRuntimeProcessesUnix() {
|
|
@@ -964,109 +1052,146 @@ function waitForProcessExit(pid, timeoutMs) {
|
|
|
964
1052
|
}
|
|
965
1053
|
return !isProcessAlive(pid);
|
|
966
1054
|
}
|
|
967
|
-
|
|
968
|
-
function readTtlMs(value, fallbackMs = 15 * 60_000) {
|
|
969
|
-
if (value === null || value === undefined || value === "") {
|
|
970
|
-
return fallbackMs;
|
|
971
|
-
}
|
|
972
|
-
const parsed = Number(value);
|
|
973
|
-
return Number.isFinite(parsed) ? parsed : fallbackMs;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
function metadataLastActivityMs(metadata) {
|
|
977
|
-
const raw = metadata?.lastActivityAt ?? metadata?.updatedAt ?? metadata?.startedAt;
|
|
978
|
-
const parsed = typeof raw === "string" ? Date.parse(raw) : NaN;
|
|
979
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
function isMetadataExpired(metadata, ttlMs, now = Date.now()) {
|
|
983
|
-
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
984
|
-
return false;
|
|
985
|
-
}
|
|
986
|
-
const lastActivityMs = metadataLastActivityMs(metadata);
|
|
987
|
-
return lastActivityMs > 0 && now - lastActivityMs > ttlMs;
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
async function tryFetch(url) {
|
|
991
|
-
try {
|
|
992
|
-
const response = await fetch(url);
|
|
993
|
-
return {
|
|
994
|
-
ok: response.ok,
|
|
995
|
-
status: response.status,
|
|
996
|
-
contentType: response.headers.get("content-type")
|
|
997
|
-
};
|
|
998
|
-
} catch (error) {
|
|
999
|
-
return {
|
|
1000
|
-
ok: false,
|
|
1001
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1055
|
+
|
|
1056
|
+
function readTtlMs(value, fallbackMs = 15 * 60_000) {
|
|
1057
|
+
if (value === null || value === undefined || value === "") {
|
|
1058
|
+
return fallbackMs;
|
|
1059
|
+
}
|
|
1060
|
+
const parsed = Number(value);
|
|
1061
|
+
return Number.isFinite(parsed) ? parsed : fallbackMs;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function metadataLastActivityMs(metadata) {
|
|
1065
|
+
const raw = metadata?.lastActivityAt ?? metadata?.updatedAt ?? metadata?.startedAt;
|
|
1066
|
+
const parsed = typeof raw === "string" ? Date.parse(raw) : NaN;
|
|
1067
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function isMetadataExpired(metadata, ttlMs, now = Date.now()) {
|
|
1071
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
const lastActivityMs = metadataLastActivityMs(metadata);
|
|
1075
|
+
return lastActivityMs > 0 && now - lastActivityMs > ttlMs;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
async function tryFetch(url) {
|
|
1079
|
+
try {
|
|
1080
|
+
const response = await fetch(url);
|
|
1081
|
+
return {
|
|
1082
|
+
ok: response.ok,
|
|
1083
|
+
status: response.status,
|
|
1084
|
+
contentType: response.headers.get("content-type")
|
|
1085
|
+
};
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
return {
|
|
1088
|
+
ok: false,
|
|
1089
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function bridgeHealthUrl(registryEntry) {
|
|
1095
|
+
if (!registryEntry || typeof registryEntry.host !== "string" || !Number.isInteger(registryEntry.port)) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return `http://${registryEntry.host}:${registryEntry.port}/health`;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
async function tryPostJson(url, body = {}) {
|
|
1103
|
+
if (!url) {
|
|
1104
|
+
return {
|
|
1105
|
+
ok: false,
|
|
1106
|
+
error: "missing url"
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
try {
|
|
1111
|
+
const response = await fetch(url, {
|
|
1112
|
+
method: "POST",
|
|
1113
|
+
headers: {
|
|
1114
|
+
"Content-Type": "application/json"
|
|
1115
|
+
},
|
|
1116
|
+
body: JSON.stringify(body)
|
|
1117
|
+
});
|
|
1118
|
+
return {
|
|
1119
|
+
ok: response.ok,
|
|
1120
|
+
status: response.status,
|
|
1121
|
+
contentType: response.headers.get("content-type")
|
|
1122
|
+
};
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
return {
|
|
1125
|
+
ok: false,
|
|
1126
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
async function waitFor(name, timeoutMs, pollMs, getState, isReady) {
|
|
1132
|
+
const startedAt = Date.now();
|
|
1133
|
+
const attempts = [];
|
|
1134
|
+
|
|
1135
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
1136
|
+
const state = await getState();
|
|
1137
|
+
attempts.push(state);
|
|
1138
|
+
if (isReady(state)) {
|
|
1139
|
+
return {
|
|
1140
|
+
ok: true,
|
|
1141
|
+
durationMs: Date.now() - startedAt,
|
|
1142
|
+
attempts: attempts.length,
|
|
1143
|
+
state
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
await sleep(pollMs);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return {
|
|
1150
|
+
ok: false,
|
|
1151
|
+
durationMs: Date.now() - startedAt,
|
|
1152
|
+
attempts: attempts.length,
|
|
1153
|
+
error: `${name} did not become ready within ${timeoutMs}ms.`,
|
|
1154
|
+
lastState: attempts.at(-1) ?? null
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
async function allocatePort() {
|
|
1159
|
+
return new Promise((resolve, reject) => {
|
|
1160
|
+
const server = net.createServer();
|
|
1161
|
+
server.once("error", reject);
|
|
1162
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1163
|
+
const address = server.address();
|
|
1164
|
+
const port = address && typeof address === "object" ? address.port : null;
|
|
1165
|
+
server.close(() => {
|
|
1166
|
+
if (Number.isInteger(port)) {
|
|
1167
|
+
resolve(port);
|
|
1168
|
+
} else {
|
|
1169
|
+
reject(new Error("Failed to allocate a TCP port."));
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
async function acquireLock(hostId, timeoutMs = 15_000, staleMs = 120_000) {
|
|
1177
|
+
const lockDir = getLockDir(hostId);
|
|
1178
|
+
const startedAt = Date.now();
|
|
1179
|
+
ensureDir(path.dirname(lockDir));
|
|
1180
|
+
|
|
1181
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
1182
|
+
try {
|
|
1183
|
+
fs.mkdirSync(lockDir);
|
|
1184
|
+
writeJson(path.join(lockDir, "owner.json"), {
|
|
1185
|
+
pid: process.pid,
|
|
1186
|
+
acquiredAt: new Date().toISOString()
|
|
1187
|
+
});
|
|
1188
|
+
return {
|
|
1189
|
+
hostId,
|
|
1190
|
+
lockDir,
|
|
1191
|
+
release() {
|
|
1192
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1070
1195
|
} catch (error) {
|
|
1071
1196
|
if (error && error.code === "EEXIST") {
|
|
1072
1197
|
const owner = readJsonFile(path.join(lockDir, "owner.json"));
|
|
@@ -1077,510 +1202,596 @@ async function acquireLock(hostId, timeoutMs = 15_000, staleMs = 120_000) {
|
|
|
1077
1202
|
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
1078
1203
|
continue;
|
|
1079
1204
|
}
|
|
1080
|
-
await sleep(250);
|
|
1081
|
-
continue;
|
|
1082
|
-
}
|
|
1083
|
-
throw error;
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
throw new Error(`Timed out acquiring managed host lock: ${lockDir}`);
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
function getMatchingRegistryEntry(metadata) {
|
|
1091
|
-
if (!metadata || typeof metadata.instanceId !== "string") {
|
|
1092
|
-
return null;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
return readRegistryEntries().find((entry) => entry.instanceId === metadata.instanceId) ?? null;
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
async function getManagedServeWebHostStatus(options = {}) {
|
|
1099
|
-
if (options.all === true) {
|
|
1100
|
-
return getAllManagedServeWebHostStatuses(options);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
|
|
1104
|
-
const codeCommand = resolveManagedIdeCommand(options);
|
|
1105
|
-
const hostKind = resolveManagedHostKind(options, codeCommand);
|
|
1106
|
-
const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
|
|
1107
|
-
const expectedVsix = options.vsix ? fingerprintFile(path.resolve(options.vsix)) : null;
|
|
1108
|
-
const metadata = readMetadataByHostId(hostId);
|
|
1109
|
-
if (!metadata) {
|
|
1110
|
-
return {
|
|
1111
|
-
ok: true,
|
|
1112
|
-
hostId,
|
|
1113
|
-
running: false,
|
|
1114
|
-
healthy: false,
|
|
1115
|
-
reason: "metadata-not-found"
|
|
1116
|
-
};
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
const registryEntry = getMatchingRegistryEntry(metadata);
|
|
1120
|
-
const rootUrl = metadata.rootUrl ?? (Number.isInteger(metadata.port) ? `http://127.0.0.1:${metadata.port}` : null);
|
|
1121
|
-
const http = rootUrl ? await tryFetch(rootUrl) : { ok: false, error: "missing rootUrl" };
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
const
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
const
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
const
|
|
1186
|
-
const
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
const
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1192
|
-
const
|
|
1193
|
-
const
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
const
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
record.ok
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
record.
|
|
1232
|
-
record.
|
|
1233
|
-
record.
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1205
|
+
await sleep(250);
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
throw new Error(`Timed out acquiring managed host lock: ${lockDir}`);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function getMatchingRegistryEntry(metadata) {
|
|
1216
|
+
if (!metadata || typeof metadata.instanceId !== "string") {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return readRegistryEntries().find((entry) => entry.instanceId === metadata.instanceId) ?? null;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
async function getManagedServeWebHostStatus(options = {}) {
|
|
1224
|
+
if (options.all === true) {
|
|
1225
|
+
return getAllManagedServeWebHostStatuses(options);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
|
|
1229
|
+
const codeCommand = resolveManagedIdeCommand(options);
|
|
1230
|
+
const hostKind = resolveManagedHostKind(options, codeCommand);
|
|
1231
|
+
const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
|
|
1232
|
+
const expectedVsix = options.vsix ? fingerprintFile(path.resolve(options.vsix)) : null;
|
|
1233
|
+
const metadata = readMetadataByHostId(hostId);
|
|
1234
|
+
if (!metadata) {
|
|
1235
|
+
return {
|
|
1236
|
+
ok: true,
|
|
1237
|
+
hostId,
|
|
1238
|
+
running: false,
|
|
1239
|
+
healthy: false,
|
|
1240
|
+
reason: "metadata-not-found"
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const registryEntry = getMatchingRegistryEntry(metadata);
|
|
1245
|
+
const rootUrl = metadata.rootUrl ?? (Number.isInteger(metadata.port) ? `http://127.0.0.1:${metadata.port}` : null);
|
|
1246
|
+
const http = rootUrl ? await tryFetch(rootUrl) : { ok: false, error: "missing rootUrl" };
|
|
1247
|
+
const bridgeHttp = registryEntry
|
|
1248
|
+
? await tryPostJson(bridgeHealthUrl(registryEntry))
|
|
1249
|
+
: { ok: false, error: "missing registryEntry" };
|
|
1250
|
+
const serveWebAlive = isProcessAlive(metadata.serveWebPid);
|
|
1251
|
+
const browserAlive = isProcessAlive(metadata.browserPid);
|
|
1252
|
+
const extensionHostAlive = registryEntry ? isProcessAlive(registryEntry.pid) : false;
|
|
1253
|
+
const workspaceMatches = registryEntry ? workspaceMatchesFixture(registryEntry, fixture) : false;
|
|
1254
|
+
const vsixMatches = expectedVsix ? fingerprintsMatch(metadata.vsixFingerprint, expectedVsix) : true;
|
|
1255
|
+
const healthy = serveWebAlive && browserAlive && extensionHostAlive && http.ok === true && bridgeHttp.ok === true && workspaceMatches && vsixMatches;
|
|
1256
|
+
|
|
1257
|
+
return {
|
|
1258
|
+
ok: true,
|
|
1259
|
+
hostId,
|
|
1260
|
+
running: serveWebAlive || browserAlive || extensionHostAlive,
|
|
1261
|
+
healthy,
|
|
1262
|
+
metadata,
|
|
1263
|
+
health: {
|
|
1264
|
+
serveWebAlive,
|
|
1265
|
+
browserAlive,
|
|
1266
|
+
extensionHostAlive,
|
|
1267
|
+
workspaceMatches,
|
|
1268
|
+
vsixMatches,
|
|
1269
|
+
expectedVsix,
|
|
1270
|
+
http,
|
|
1271
|
+
bridgeHttp,
|
|
1272
|
+
registryEntry
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
async function getAllManagedServeWebHostStatuses(options = {}) {
|
|
1278
|
+
const entries = readAllManagedHostEntries();
|
|
1279
|
+
const runtimes = [];
|
|
1280
|
+
for (const entry of entries) {
|
|
1281
|
+
const metadata = entry.metadata;
|
|
1282
|
+
const status = await getManagedServeWebHostStatus({
|
|
1283
|
+
...options,
|
|
1284
|
+
all: false,
|
|
1285
|
+
hostId: entry.hostId,
|
|
1286
|
+
fixture: metadata.fixture ?? defaultFixtureDir,
|
|
1287
|
+
vsix: metadata.vsix,
|
|
1288
|
+
hostKind: metadata.hostKind
|
|
1289
|
+
});
|
|
1290
|
+
runtimes.push(compactRuntimeStatus(status, metadata));
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
ok: true,
|
|
1294
|
+
all: true,
|
|
1295
|
+
count: runtimes.length,
|
|
1296
|
+
running: runtimes.filter((runtime) => runtime.running === true).length,
|
|
1297
|
+
healthy: runtimes.filter((runtime) => runtime.healthy === true).length,
|
|
1298
|
+
runtimes
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
async function startManagedServeWebHost(options = {}) {
|
|
1303
|
+
const startedAt = Date.now();
|
|
1304
|
+
const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
|
|
1305
|
+
const ttlMs = readTtlMs(options.ttlMs);
|
|
1306
|
+
const intervalMs = Number.isFinite(Number(options.intervalMs)) && Number(options.intervalMs) > 0
|
|
1307
|
+
? Number(options.intervalMs)
|
|
1308
|
+
: 30_000;
|
|
1309
|
+
const waitMs = Number.isInteger(options.waitMs) ? options.waitMs : 45_000;
|
|
1310
|
+
const pollMs = Number.isInteger(options.pollMs) ? options.pollMs : 500;
|
|
1311
|
+
const vsix = path.resolve(options.vsix ?? defaultVsixPath);
|
|
1312
|
+
let vsixFingerprint = null;
|
|
1313
|
+
const reuse = options.reuse !== false;
|
|
1314
|
+
const codeCommand = resolveManagedIdeCommand(options);
|
|
1315
|
+
const hostKind = resolveManagedHostKind(options, codeCommand);
|
|
1316
|
+
const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
|
|
1317
|
+
const runDir = path.resolve(options.runDir ?? path.join(defaultRunsDir, `${hostId}-${timestampForPath()}`));
|
|
1318
|
+
const logsDir = path.join(runDir, "logs");
|
|
1319
|
+
const stepsDir = path.join(runDir, "steps");
|
|
1320
|
+
const userDataDir = path.join(runDir, "user-data");
|
|
1321
|
+
const serverDataDir = path.join(runDir, "server-data");
|
|
1322
|
+
const extensionsDir = path.join(runDir, "extensions");
|
|
1323
|
+
const activeExtensionsDir = hostKind === "code-server" || process.platform === "win32"
|
|
1324
|
+
? extensionsDir
|
|
1325
|
+
: path.join(serverDataDir, "extensions");
|
|
1326
|
+
const ideEnv = buildIdeCommandEnv(runDir);
|
|
1327
|
+
const steps = [];
|
|
1328
|
+
const failures = [];
|
|
1329
|
+
const beforeRegistry = readRegistryEntries();
|
|
1330
|
+
const beforeInstanceIds = new Set(beforeRegistry.map((entry) => entry.instanceId));
|
|
1331
|
+
let serveWebProcess = null;
|
|
1332
|
+
let browserProcess = null;
|
|
1333
|
+
let metadata = null;
|
|
1334
|
+
|
|
1335
|
+
ensureDir(logsDir);
|
|
1336
|
+
ensureDir(stepsDir);
|
|
1337
|
+
await cleanupExpiredManagedServeWebHosts({ ttlMs });
|
|
1338
|
+
|
|
1339
|
+
async function step(name, fn) {
|
|
1340
|
+
const stepIndex = String(steps.length + 1).padStart(2, "0");
|
|
1341
|
+
const stepPath = path.join(stepsDir, `${stepIndex}-${name.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}.json`);
|
|
1342
|
+
const startedStepAt = Date.now();
|
|
1343
|
+
const record = {
|
|
1344
|
+
name,
|
|
1345
|
+
ok: false,
|
|
1346
|
+
startedAt: new Date(startedStepAt).toISOString()
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
try {
|
|
1350
|
+
record.detail = await fn();
|
|
1351
|
+
record.ok = record.detail?.ok !== false;
|
|
1352
|
+
if (!record.ok) {
|
|
1353
|
+
failures.push(`${name}: ${record.detail?.error ?? "failed"}`);
|
|
1354
|
+
}
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
record.ok = false;
|
|
1357
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
1358
|
+
failures.push(`${name}: ${record.error}`);
|
|
1359
|
+
} finally {
|
|
1360
|
+
record.durationMs = Date.now() - startedStepAt;
|
|
1361
|
+
record.finishedAt = new Date().toISOString();
|
|
1362
|
+
record.path = stepPath;
|
|
1363
|
+
writeJson(stepPath, record);
|
|
1364
|
+
steps.push(record);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
return record;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1241
1370
|
const lock = await acquireLock(hostId, Math.max(90_000, waitMs + 45_000));
|
|
1242
1371
|
try {
|
|
1372
|
+
await step("ensure fresh extension vsix", async () => {
|
|
1373
|
+
const freshness = getExtensionVsixFreshness(vsix);
|
|
1374
|
+
if (!freshness.supported) {
|
|
1375
|
+
vsixFingerprint = fingerprintFile(vsix);
|
|
1376
|
+
return {
|
|
1377
|
+
ok: true,
|
|
1378
|
+
skipped: true,
|
|
1379
|
+
reason: freshness.reason,
|
|
1380
|
+
vsix: freshness.vsix,
|
|
1381
|
+
vsixFingerprint
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (!freshness.stale) {
|
|
1386
|
+
vsixFingerprint = fingerprintFile(vsix);
|
|
1387
|
+
return {
|
|
1388
|
+
ok: true,
|
|
1389
|
+
packaged: false,
|
|
1390
|
+
stale: false,
|
|
1391
|
+
vsix: freshness.vsix,
|
|
1392
|
+
vsixMtimeMs: freshness.vsixMtimeMs,
|
|
1393
|
+
newestInput: freshness.newestInput,
|
|
1394
|
+
vsixFingerprint
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const packageLock = await acquireLock("extension-vsix-package", 180_000, 300_000);
|
|
1399
|
+
try {
|
|
1400
|
+
const lockedFreshness = getExtensionVsixFreshness(vsix);
|
|
1401
|
+
if (lockedFreshness.supported && !lockedFreshness.stale) {
|
|
1402
|
+
vsixFingerprint = fingerprintFile(vsix);
|
|
1403
|
+
return {
|
|
1404
|
+
ok: true,
|
|
1405
|
+
packaged: false,
|
|
1406
|
+
packagedByOtherProcess: true,
|
|
1407
|
+
stale: false,
|
|
1408
|
+
vsix: lockedFreshness.vsix,
|
|
1409
|
+
vsixMtimeMs: lockedFreshness.vsixMtimeMs,
|
|
1410
|
+
newestInput: lockedFreshness.newestInput,
|
|
1411
|
+
vsixFingerprint
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
const packageResult = packageExtensionVsix();
|
|
1416
|
+
vsixFingerprint = fingerprintFile(vsix);
|
|
1417
|
+
return {
|
|
1418
|
+
ok: packageResult.status === 0 && Boolean(vsixFingerprint),
|
|
1419
|
+
packaged: true,
|
|
1420
|
+
stale: true,
|
|
1421
|
+
status: packageResult.status,
|
|
1422
|
+
error: packageResult.error ?? null,
|
|
1423
|
+
stdoutTail: tail(packageResult.stdout),
|
|
1424
|
+
stderrTail: tail(packageResult.stderr),
|
|
1425
|
+
durationMs: packageResult.durationMs,
|
|
1426
|
+
vsix: freshness.vsix,
|
|
1427
|
+
previousVsixMtimeMs: freshness.vsixMtimeMs,
|
|
1428
|
+
newestInput: freshness.newestInput,
|
|
1429
|
+
vsixFingerprint
|
|
1430
|
+
};
|
|
1431
|
+
} finally {
|
|
1432
|
+
packageLock.release();
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
if (failures.length > 0) {
|
|
1436
|
+
return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup: null });
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1243
1439
|
const orphanCleanup = readMetadataByHostId(hostId) ? null : stopProcessesByCommandLineFragment(hostId);
|
|
1244
1440
|
if (reuse) {
|
|
1245
|
-
const existingStatus = await getManagedServeWebHostStatus({ fixture, hostId, vsix, ideCommand: options.ideCommand, hostKind });
|
|
1246
|
-
if (existingStatus.healthy) {
|
|
1247
|
-
const updatedMetadata = {
|
|
1248
|
-
...existingStatus.metadata,
|
|
1249
|
-
updatedAt: new Date().toISOString(),
|
|
1250
|
-
lastActivityAt: new Date().toISOString(),
|
|
1251
|
-
ttlMs,
|
|
1252
|
-
watchdogIntervalMs: intervalMs
|
|
1253
|
-
};
|
|
1254
|
-
writeMetadata(hostId, updatedMetadata);
|
|
1255
|
-
startManagedServeWebWatchdog({ fixture, hostId, ttlMs, intervalMs, logsDir: existingStatus.metadata.logsDir ?? logsDir, ideCommand: options.ideCommand, hostKind });
|
|
1256
|
-
return buildStartSummary({
|
|
1257
|
-
ok: true,
|
|
1258
|
-
reused: true,
|
|
1259
|
-
startedAt,
|
|
1260
|
-
hostId,
|
|
1261
|
-
fixture,
|
|
1262
|
-
vsix,
|
|
1263
|
-
codeCommand,
|
|
1264
|
-
hostKind,
|
|
1265
|
-
runDir: existingStatus.metadata.runDir ?? runDir,
|
|
1266
|
-
logsDir: existingStatus.metadata.logsDir ?? logsDir,
|
|
1267
|
-
stepsDir: existingStatus.metadata.stepsDir ?? stepsDir,
|
|
1441
|
+
const existingStatus = await getManagedServeWebHostStatus({ fixture, hostId, vsix, ideCommand: options.ideCommand, hostKind });
|
|
1442
|
+
if (existingStatus.healthy) {
|
|
1443
|
+
const updatedMetadata = {
|
|
1444
|
+
...existingStatus.metadata,
|
|
1445
|
+
updatedAt: new Date().toISOString(),
|
|
1446
|
+
lastActivityAt: new Date().toISOString(),
|
|
1447
|
+
ttlMs,
|
|
1448
|
+
watchdogIntervalMs: intervalMs
|
|
1449
|
+
};
|
|
1450
|
+
writeMetadata(hostId, updatedMetadata);
|
|
1451
|
+
startManagedServeWebWatchdog({ fixture, hostId, ttlMs, intervalMs, logsDir: existingStatus.metadata.logsDir ?? logsDir, ideCommand: options.ideCommand, hostKind });
|
|
1452
|
+
return buildStartSummary({
|
|
1453
|
+
ok: true,
|
|
1454
|
+
reused: true,
|
|
1455
|
+
startedAt,
|
|
1456
|
+
hostId,
|
|
1457
|
+
fixture,
|
|
1458
|
+
vsix,
|
|
1459
|
+
codeCommand,
|
|
1460
|
+
hostKind,
|
|
1461
|
+
runDir: existingStatus.metadata.runDir ?? runDir,
|
|
1462
|
+
logsDir: existingStatus.metadata.logsDir ?? logsDir,
|
|
1463
|
+
stepsDir: existingStatus.metadata.stepsDir ?? stepsDir,
|
|
1268
1464
|
metadata: updatedMetadata,
|
|
1269
1465
|
steps,
|
|
1270
1466
|
failures,
|
|
1271
1467
|
orphanCleanup
|
|
1272
1468
|
});
|
|
1273
1469
|
}
|
|
1274
|
-
if (existingStatus.metadata) {
|
|
1275
|
-
await stopManagedServeWebHost({ fixture, hostId, removeMetadataOnlyIfKnown: true });
|
|
1276
|
-
}
|
|
1277
|
-
} else {
|
|
1278
|
-
await stopManagedServeWebHost({ fixture, hostId, removeMetadataOnlyIfKnown: true });
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
await step("prepare profile", async () => {
|
|
1282
|
-
ensureDir(path.join(runDir, "home"));
|
|
1283
|
-
ensureDir(path.join(runDir, "cli-data"));
|
|
1284
|
-
ensureDir(path.join(userDataDir, "User"));
|
|
1285
|
-
ensureDir(path.join(serverDataDir, "data", "User"));
|
|
1286
|
-
ensureDir(serverDataDir);
|
|
1287
|
-
ensureDir(extensionsDir);
|
|
1288
|
-
ensureDir(activeExtensionsDir);
|
|
1289
|
-
const settings = {
|
|
1290
|
-
"security.workspace.trust.enabled": false,
|
|
1291
|
-
"workbench.startupEditor": "none"
|
|
1292
|
-
};
|
|
1293
|
-
writeJson(path.join(userDataDir, "User", "settings.json"), settings);
|
|
1294
|
-
writeJson(path.join(serverDataDir, "data", "User", "settings.json"), settings);
|
|
1295
|
-
return {
|
|
1296
|
-
ok: true,
|
|
1297
|
-
runDir,
|
|
1298
|
-
userDataDir,
|
|
1299
|
-
serverDataDir,
|
|
1300
|
-
extensionsDir
|
|
1301
|
-
};
|
|
1302
|
-
});
|
|
1303
|
-
|
|
1304
|
-
await step("install extension", async () => {
|
|
1305
|
-
if (!fs.existsSync(vsix)) {
|
|
1306
|
-
return {
|
|
1307
|
-
ok: false,
|
|
1308
|
-
error: `VSIX does not exist: ${vsix}`
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
const result = runCommand(codeCommand, [
|
|
1312
|
-
"--extensions-dir",
|
|
1313
|
-
activeExtensionsDir,
|
|
1314
|
-
"--install-extension",
|
|
1315
|
-
vsix,
|
|
1316
|
-
"--force"
|
|
1317
|
-
], { timeoutMs: 60_000, env: ideEnv });
|
|
1318
|
-
const stdoutPath = path.join(logsDir, "install-extension.stdout.txt");
|
|
1319
|
-
const stderrPath = path.join(logsDir, "install-extension.stderr.txt");
|
|
1320
|
-
writeText(stdoutPath, result.stdout);
|
|
1321
|
-
writeText(stderrPath, result.stderr);
|
|
1322
|
-
return {
|
|
1323
|
-
ok: result.status === 0,
|
|
1324
|
-
status: result.status,
|
|
1325
|
-
durationMs: result.durationMs,
|
|
1326
|
-
stdoutPath,
|
|
1327
|
-
stderrPath,
|
|
1328
|
-
stdoutTail: tail(result.stdout),
|
|
1329
|
-
stderrTail: tail(result.stderr),
|
|
1330
|
-
error: result.error
|
|
1331
|
-
};
|
|
1332
|
-
});
|
|
1333
|
-
if (failures.length > 0) {
|
|
1470
|
+
if (existingStatus.metadata) {
|
|
1471
|
+
await stopManagedServeWebHost({ fixture, hostId, removeMetadataOnlyIfKnown: true });
|
|
1472
|
+
}
|
|
1473
|
+
} else {
|
|
1474
|
+
await stopManagedServeWebHost({ fixture, hostId, removeMetadataOnlyIfKnown: true });
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
await step("prepare profile", async () => {
|
|
1478
|
+
ensureDir(path.join(runDir, "home"));
|
|
1479
|
+
ensureDir(path.join(runDir, "cli-data"));
|
|
1480
|
+
ensureDir(path.join(userDataDir, "User"));
|
|
1481
|
+
ensureDir(path.join(serverDataDir, "data", "User"));
|
|
1482
|
+
ensureDir(serverDataDir);
|
|
1483
|
+
ensureDir(extensionsDir);
|
|
1484
|
+
ensureDir(activeExtensionsDir);
|
|
1485
|
+
const settings = {
|
|
1486
|
+
"security.workspace.trust.enabled": false,
|
|
1487
|
+
"workbench.startupEditor": "none"
|
|
1488
|
+
};
|
|
1489
|
+
writeJson(path.join(userDataDir, "User", "settings.json"), settings);
|
|
1490
|
+
writeJson(path.join(serverDataDir, "data", "User", "settings.json"), settings);
|
|
1491
|
+
return {
|
|
1492
|
+
ok: true,
|
|
1493
|
+
runDir,
|
|
1494
|
+
userDataDir,
|
|
1495
|
+
serverDataDir,
|
|
1496
|
+
extensionsDir
|
|
1497
|
+
};
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
await step("install extension", async () => {
|
|
1501
|
+
if (!fs.existsSync(vsix)) {
|
|
1502
|
+
return {
|
|
1503
|
+
ok: false,
|
|
1504
|
+
error: `VSIX does not exist: ${vsix}`
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
const result = runCommand(codeCommand, [
|
|
1508
|
+
"--extensions-dir",
|
|
1509
|
+
activeExtensionsDir,
|
|
1510
|
+
"--install-extension",
|
|
1511
|
+
vsix,
|
|
1512
|
+
"--force"
|
|
1513
|
+
], { timeoutMs: 60_000, env: ideEnv });
|
|
1514
|
+
const stdoutPath = path.join(logsDir, "install-extension.stdout.txt");
|
|
1515
|
+
const stderrPath = path.join(logsDir, "install-extension.stderr.txt");
|
|
1516
|
+
writeText(stdoutPath, result.stdout);
|
|
1517
|
+
writeText(stderrPath, result.stderr);
|
|
1518
|
+
return {
|
|
1519
|
+
ok: result.status === 0,
|
|
1520
|
+
status: result.status,
|
|
1521
|
+
durationMs: result.durationMs,
|
|
1522
|
+
stdoutPath,
|
|
1523
|
+
stderrPath,
|
|
1524
|
+
stdoutTail: tail(result.stdout),
|
|
1525
|
+
stderrTail: tail(result.stderr),
|
|
1526
|
+
error: result.error
|
|
1527
|
+
};
|
|
1528
|
+
});
|
|
1529
|
+
if (failures.length > 0) {
|
|
1334
1530
|
return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup });
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
const port = Number.isInteger(options.port) ? options.port : await allocatePort();
|
|
1338
|
-
const rootUrl = `http://127.0.0.1:${port}`;
|
|
1339
|
-
|
|
1340
|
-
const serveWebStdoutPath = path.join(logsDir, "serve-web.stdout.txt");
|
|
1341
|
-
const serveWebStderrPath = path.join(logsDir, "serve-web.stderr.txt");
|
|
1342
|
-
|
|
1343
|
-
await step("start serve-web", async () => {
|
|
1344
|
-
const args = hostKind === "code-server"
|
|
1345
|
-
? [
|
|
1346
|
-
"--auth",
|
|
1347
|
-
"none",
|
|
1348
|
-
"--bind-addr",
|
|
1349
|
-
`127.0.0.1:${port}`,
|
|
1350
|
-
"--disable-workspace-trust",
|
|
1351
|
-
"--disable-telemetry",
|
|
1352
|
-
"--disable-update-check",
|
|
1353
|
-
"--user-data-dir",
|
|
1354
|
-
userDataDir,
|
|
1355
|
-
"--extensions-dir",
|
|
1356
|
-
activeExtensionsDir,
|
|
1357
|
-
fixture
|
|
1358
|
-
]
|
|
1359
|
-
: [
|
|
1360
|
-
"--user-data-dir",
|
|
1361
|
-
userDataDir,
|
|
1362
|
-
...(process.platform === "win32" ? ["--extensions-dir", activeExtensionsDir] : []),
|
|
1363
|
-
"serve-web",
|
|
1364
|
-
"--accept-server-license-terms",
|
|
1365
|
-
"--host",
|
|
1366
|
-
"127.0.0.1",
|
|
1367
|
-
"--port",
|
|
1368
|
-
String(port),
|
|
1369
|
-
"--without-connection-token",
|
|
1370
|
-
"--default-folder",
|
|
1371
|
-
fixture,
|
|
1372
|
-
"--server-data-dir",
|
|
1373
|
-
serverDataDir,
|
|
1374
|
-
"--log",
|
|
1375
|
-
"trace",
|
|
1376
|
-
...(process.platform !== "win32" ? ["--cli-data-dir", ideEnv.VSCODE_CLI_DATA_DIR ?? path.join(runDir, "cli-data")] : [])
|
|
1377
|
-
];
|
|
1378
|
-
serveWebProcess = startLoggedCommand(codeCommand, args, {
|
|
1379
|
-
stdoutPath: serveWebStdoutPath,
|
|
1380
|
-
stderrPath: serveWebStderrPath,
|
|
1381
|
-
detached: true,
|
|
1382
|
-
env: ideEnv
|
|
1383
|
-
});
|
|
1384
|
-
return {
|
|
1385
|
-
ok: true,
|
|
1386
|
-
pid: serveWebProcess.child.pid ?? null,
|
|
1387
|
-
command: commandDisplayName(codeCommand),
|
|
1388
|
-
commandSource: codeCommand.source ?? null,
|
|
1389
|
-
hostKind,
|
|
1390
|
-
args,
|
|
1391
|
-
stdoutPath: serveWebStdoutPath,
|
|
1392
|
-
stderrPath: serveWebStderrPath
|
|
1393
|
-
};
|
|
1394
|
-
});
|
|
1395
|
-
|
|
1396
|
-
await step("wait for serve-web http", async () => {
|
|
1397
|
-
return waitFor(
|
|
1398
|
-
"serve-web http",
|
|
1399
|
-
waitMs,
|
|
1400
|
-
pollMs,
|
|
1401
|
-
() => tryFetch(rootUrl),
|
|
1402
|
-
(state) => state.ok === true
|
|
1403
|
-
);
|
|
1404
|
-
});
|
|
1405
|
-
|
|
1406
|
-
await step("wait for extension host agent", async () => {
|
|
1407
|
-
return waitFor(
|
|
1408
|
-
"extension host agent",
|
|
1409
|
-
waitMs,
|
|
1410
|
-
pollMs,
|
|
1411
|
-
() => {
|
|
1412
|
-
const stdout = readTextIfExists(serveWebStdoutPath);
|
|
1413
|
-
const stderr = readTextIfExists(serveWebStderrPath);
|
|
1414
|
-
const userDataLogs = readRecentLogText(path.join(userDataDir, "logs"));
|
|
1415
|
-
const serverDataLogs = readRecentLogText(path.join(serverDataDir, "logs"));
|
|
1416
|
-
const combined = `${stdout}\n${stderr}\n${userDataLogs}\n${serverDataLogs}`;
|
|
1417
|
-
return {
|
|
1418
|
-
ready: combined.includes("Extension host agent started")
|
|
1419
|
-
|| combined.includes("Extension host agent listening"),
|
|
1420
|
-
stdoutTail: tail(stdout, 1000),
|
|
1421
|
-
stderrTail: tail(stderr, 1000),
|
|
1422
|
-
logTail: tail(`${userDataLogs}\n${serverDataLogs}`, 1000)
|
|
1423
|
-
};
|
|
1424
|
-
},
|
|
1425
|
-
(state) => state.ready === true
|
|
1426
|
-
);
|
|
1427
|
-
});
|
|
1428
|
-
if (failures.length > 0) {
|
|
1429
|
-
stopProcessTree(serveWebProcess?.child?.pid);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
const port = Number.isInteger(options.port) ? options.port : await allocatePort();
|
|
1534
|
+
const rootUrl = `http://127.0.0.1:${port}`;
|
|
1535
|
+
|
|
1536
|
+
const serveWebStdoutPath = path.join(logsDir, "serve-web.stdout.txt");
|
|
1537
|
+
const serveWebStderrPath = path.join(logsDir, "serve-web.stderr.txt");
|
|
1538
|
+
|
|
1539
|
+
await step("start serve-web", async () => {
|
|
1540
|
+
const args = hostKind === "code-server"
|
|
1541
|
+
? [
|
|
1542
|
+
"--auth",
|
|
1543
|
+
"none",
|
|
1544
|
+
"--bind-addr",
|
|
1545
|
+
`127.0.0.1:${port}`,
|
|
1546
|
+
"--disable-workspace-trust",
|
|
1547
|
+
"--disable-telemetry",
|
|
1548
|
+
"--disable-update-check",
|
|
1549
|
+
"--user-data-dir",
|
|
1550
|
+
userDataDir,
|
|
1551
|
+
"--extensions-dir",
|
|
1552
|
+
activeExtensionsDir,
|
|
1553
|
+
fixture
|
|
1554
|
+
]
|
|
1555
|
+
: [
|
|
1556
|
+
"--user-data-dir",
|
|
1557
|
+
userDataDir,
|
|
1558
|
+
...(process.platform === "win32" ? ["--extensions-dir", activeExtensionsDir] : []),
|
|
1559
|
+
"serve-web",
|
|
1560
|
+
"--accept-server-license-terms",
|
|
1561
|
+
"--host",
|
|
1562
|
+
"127.0.0.1",
|
|
1563
|
+
"--port",
|
|
1564
|
+
String(port),
|
|
1565
|
+
"--without-connection-token",
|
|
1566
|
+
"--default-folder",
|
|
1567
|
+
fixture,
|
|
1568
|
+
"--server-data-dir",
|
|
1569
|
+
serverDataDir,
|
|
1570
|
+
"--log",
|
|
1571
|
+
"trace",
|
|
1572
|
+
...(process.platform !== "win32" ? ["--cli-data-dir", ideEnv.VSCODE_CLI_DATA_DIR ?? path.join(runDir, "cli-data")] : [])
|
|
1573
|
+
];
|
|
1574
|
+
serveWebProcess = startLoggedCommand(codeCommand, args, {
|
|
1575
|
+
stdoutPath: serveWebStdoutPath,
|
|
1576
|
+
stderrPath: serveWebStderrPath,
|
|
1577
|
+
detached: true,
|
|
1578
|
+
env: ideEnv
|
|
1579
|
+
});
|
|
1580
|
+
return {
|
|
1581
|
+
ok: true,
|
|
1582
|
+
pid: serveWebProcess.child.pid ?? null,
|
|
1583
|
+
command: commandDisplayName(codeCommand),
|
|
1584
|
+
commandSource: codeCommand.source ?? null,
|
|
1585
|
+
hostKind,
|
|
1586
|
+
args,
|
|
1587
|
+
stdoutPath: serveWebStdoutPath,
|
|
1588
|
+
stderrPath: serveWebStderrPath
|
|
1589
|
+
};
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
await step("wait for serve-web http", async () => {
|
|
1593
|
+
return waitFor(
|
|
1594
|
+
"serve-web http",
|
|
1595
|
+
waitMs,
|
|
1596
|
+
pollMs,
|
|
1597
|
+
() => tryFetch(rootUrl),
|
|
1598
|
+
(state) => state.ok === true
|
|
1599
|
+
);
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
await step("wait for extension host agent", async () => {
|
|
1603
|
+
return waitFor(
|
|
1604
|
+
"extension host agent",
|
|
1605
|
+
waitMs,
|
|
1606
|
+
pollMs,
|
|
1607
|
+
() => {
|
|
1608
|
+
const stdout = readTextIfExists(serveWebStdoutPath);
|
|
1609
|
+
const stderr = readTextIfExists(serveWebStderrPath);
|
|
1610
|
+
const userDataLogs = readRecentLogText(path.join(userDataDir, "logs"));
|
|
1611
|
+
const serverDataLogs = readRecentLogText(path.join(serverDataDir, "logs"));
|
|
1612
|
+
const combined = `${stdout}\n${stderr}\n${userDataLogs}\n${serverDataLogs}`;
|
|
1613
|
+
return {
|
|
1614
|
+
ready: combined.includes("Extension host agent started")
|
|
1615
|
+
|| combined.includes("Extension host agent listening"),
|
|
1616
|
+
stdoutTail: tail(stdout, 1000),
|
|
1617
|
+
stderrTail: tail(stderr, 1000),
|
|
1618
|
+
logTail: tail(`${userDataLogs}\n${serverDataLogs}`, 1000)
|
|
1619
|
+
};
|
|
1620
|
+
},
|
|
1621
|
+
(state) => state.ready === true
|
|
1622
|
+
);
|
|
1623
|
+
});
|
|
1624
|
+
if (failures.length > 0) {
|
|
1625
|
+
stopProcessTree(serveWebProcess?.child?.pid);
|
|
1430
1626
|
return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup });
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
await step("open headless browser client", async () => {
|
|
1434
|
-
const chromePath = resolveChromePath(options.chromePath);
|
|
1435
|
-
if (!chromePath) {
|
|
1436
|
-
return {
|
|
1437
|
-
ok: false,
|
|
1438
|
-
error: "Chrome or Edge executable was not found."
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
const profileDir = path.join(runDir, "headless-browser-profile");
|
|
1443
|
-
const stdoutPath = path.join(logsDir, "headless-browser.stdout.txt");
|
|
1444
|
-
const stderrPath = path.join(logsDir, "headless-browser.stderr.txt");
|
|
1445
|
-
const url = `${rootUrl}/?folder=${encodeURIComponent(fixture)}`;
|
|
1446
|
-
const browserArgs = [
|
|
1447
|
-
"--headless=new",
|
|
1448
|
-
"--disable-gpu",
|
|
1449
|
-
"--disable-dev-shm-usage",
|
|
1450
|
-
"--disable-background-networking",
|
|
1451
|
-
"--disable-sync",
|
|
1452
|
-
"--no-sandbox",
|
|
1453
|
-
"--remote-debugging-port=0",
|
|
1454
|
-
"--no-first-run",
|
|
1455
|
-
"--no-default-browser-check",
|
|
1456
|
-
`--user-data-dir=${profileDir}`,
|
|
1457
|
-
url
|
|
1458
|
-
];
|
|
1459
|
-
if (process.platform === "win32") {
|
|
1460
|
-
const scriptPath = writeWindowsDetachedScript(chromePath, browserArgs, {
|
|
1461
|
-
cwd: rootDir,
|
|
1462
|
-
stdoutPath,
|
|
1463
|
-
stderrPath
|
|
1464
|
-
});
|
|
1465
|
-
browserProcess = startWindowsHiddenScript(scriptPath, {
|
|
1466
|
-
cwd: rootDir,
|
|
1467
|
-
stdoutPath,
|
|
1468
|
-
stderrPath
|
|
1469
|
-
});
|
|
1470
|
-
} else {
|
|
1471
|
-
const stdoutFd = fs.openSync(stdoutPath, "a");
|
|
1472
|
-
const stderrFd = fs.openSync(stderrPath, "a");
|
|
1473
|
-
browserProcess = spawn(chromePath, browserArgs, {
|
|
1474
|
-
cwd: rootDir,
|
|
1475
|
-
detached: true,
|
|
1476
|
-
windowsHide: true,
|
|
1477
|
-
stdio: ["ignore", stdoutFd, stderrFd]
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
browserProcess.on("error", () => {
|
|
1481
|
-
// Later registry/health waits surface browser startup failures.
|
|
1482
|
-
});
|
|
1483
|
-
browserProcess.unref();
|
|
1484
|
-
|
|
1485
|
-
return {
|
|
1486
|
-
ok: true,
|
|
1487
|
-
pid: browserProcess.pid ?? null,
|
|
1488
|
-
chromePath,
|
|
1489
|
-
profileDir,
|
|
1490
|
-
devToolsActivePortPath: path.join(profileDir, "DevToolsActivePort"),
|
|
1491
|
-
stdoutPath,
|
|
1492
|
-
stderrPath,
|
|
1493
|
-
url
|
|
1494
|
-
};
|
|
1495
|
-
});
|
|
1496
|
-
|
|
1497
|
-
await step("wait for bridge registry entry", async () => {
|
|
1498
|
-
const waitResult = await waitFor(
|
|
1499
|
-
"fresh bridge registry entry",
|
|
1500
|
-
waitMs,
|
|
1501
|
-
pollMs,
|
|
1502
|
-
() => {
|
|
1503
|
-
const entries = readRegistryEntries();
|
|
1504
|
-
const matchingFreshEntries = entries.filter(
|
|
1505
|
-
(entry) => !beforeInstanceIds.has(entry.instanceId) && workspaceMatchesFixture(entry, fixture)
|
|
1506
|
-
);
|
|
1507
|
-
return {
|
|
1508
|
-
ok: matchingFreshEntries.length > 0,
|
|
1509
|
-
entry: matchingFreshEntries[0] ?? null,
|
|
1510
|
-
entryCount: entries.length,
|
|
1511
|
-
matchingFreshCount: matchingFreshEntries.length
|
|
1512
|
-
};
|
|
1513
|
-
},
|
|
1514
|
-
(state) => state.ok === true
|
|
1515
|
-
);
|
|
1516
|
-
return waitResult;
|
|
1517
|
-
});
|
|
1518
|
-
if (failures.length > 0) {
|
|
1519
|
-
stopProcessTree(browserProcess?.pid);
|
|
1520
|
-
stopProcessTree(serveWebProcess?.child?.pid);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
await step("open headless browser client", async () => {
|
|
1630
|
+
const chromePath = resolveChromePath(options.chromePath);
|
|
1631
|
+
if (!chromePath) {
|
|
1632
|
+
return {
|
|
1633
|
+
ok: false,
|
|
1634
|
+
error: "Chrome or Edge executable was not found."
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const profileDir = path.join(runDir, "headless-browser-profile");
|
|
1639
|
+
const stdoutPath = path.join(logsDir, "headless-browser.stdout.txt");
|
|
1640
|
+
const stderrPath = path.join(logsDir, "headless-browser.stderr.txt");
|
|
1641
|
+
const url = `${rootUrl}/?folder=${encodeURIComponent(fixture)}`;
|
|
1642
|
+
const browserArgs = [
|
|
1643
|
+
"--headless=new",
|
|
1644
|
+
"--disable-gpu",
|
|
1645
|
+
"--disable-dev-shm-usage",
|
|
1646
|
+
"--disable-background-networking",
|
|
1647
|
+
"--disable-sync",
|
|
1648
|
+
"--no-sandbox",
|
|
1649
|
+
"--remote-debugging-port=0",
|
|
1650
|
+
"--no-first-run",
|
|
1651
|
+
"--no-default-browser-check",
|
|
1652
|
+
`--user-data-dir=${profileDir}`,
|
|
1653
|
+
url
|
|
1654
|
+
];
|
|
1655
|
+
if (process.platform === "win32") {
|
|
1656
|
+
const scriptPath = writeWindowsDetachedScript(chromePath, browserArgs, {
|
|
1657
|
+
cwd: rootDir,
|
|
1658
|
+
stdoutPath,
|
|
1659
|
+
stderrPath
|
|
1660
|
+
});
|
|
1661
|
+
browserProcess = startWindowsHiddenScript(scriptPath, {
|
|
1662
|
+
cwd: rootDir,
|
|
1663
|
+
stdoutPath,
|
|
1664
|
+
stderrPath
|
|
1665
|
+
});
|
|
1666
|
+
} else {
|
|
1667
|
+
const stdoutFd = fs.openSync(stdoutPath, "a");
|
|
1668
|
+
const stderrFd = fs.openSync(stderrPath, "a");
|
|
1669
|
+
browserProcess = spawn(chromePath, browserArgs, {
|
|
1670
|
+
cwd: rootDir,
|
|
1671
|
+
detached: true,
|
|
1672
|
+
windowsHide: true,
|
|
1673
|
+
stdio: ["ignore", stdoutFd, stderrFd]
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
browserProcess.on("error", () => {
|
|
1677
|
+
// Later registry/health waits surface browser startup failures.
|
|
1678
|
+
});
|
|
1679
|
+
browserProcess.unref();
|
|
1680
|
+
|
|
1681
|
+
return {
|
|
1682
|
+
ok: true,
|
|
1683
|
+
pid: browserProcess.pid ?? null,
|
|
1684
|
+
chromePath,
|
|
1685
|
+
profileDir,
|
|
1686
|
+
devToolsActivePortPath: path.join(profileDir, "DevToolsActivePort"),
|
|
1687
|
+
stdoutPath,
|
|
1688
|
+
stderrPath,
|
|
1689
|
+
url
|
|
1690
|
+
};
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
await step("wait for bridge registry entry", async () => {
|
|
1694
|
+
const waitResult = await waitFor(
|
|
1695
|
+
"fresh bridge registry entry",
|
|
1696
|
+
waitMs,
|
|
1697
|
+
pollMs,
|
|
1698
|
+
() => {
|
|
1699
|
+
const entries = readRegistryEntries();
|
|
1700
|
+
const matchingFreshEntries = entries.filter(
|
|
1701
|
+
(entry) => !beforeInstanceIds.has(entry.instanceId) && workspaceMatchesFixture(entry, fixture)
|
|
1702
|
+
);
|
|
1703
|
+
return {
|
|
1704
|
+
ok: matchingFreshEntries.length > 0,
|
|
1705
|
+
entry: matchingFreshEntries[0] ?? null,
|
|
1706
|
+
entryCount: entries.length,
|
|
1707
|
+
matchingFreshCount: matchingFreshEntries.length
|
|
1708
|
+
};
|
|
1709
|
+
},
|
|
1710
|
+
(state) => state.ok === true
|
|
1711
|
+
);
|
|
1712
|
+
return waitResult;
|
|
1713
|
+
});
|
|
1714
|
+
if (failures.length > 0) {
|
|
1715
|
+
stopProcessTree(browserProcess?.pid);
|
|
1716
|
+
stopProcessTree(serveWebProcess?.child?.pid);
|
|
1521
1717
|
return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, steps, failures, orphanCleanup });
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
const registryEntry = steps.at(-1)?.detail?.state?.entry ?? null;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
const registryEntry = steps.at(-1)?.detail?.state?.entry ?? null;
|
|
1721
|
+
await step("wait for bridge http health", async () => {
|
|
1722
|
+
return waitFor(
|
|
1723
|
+
"bridge http health",
|
|
1724
|
+
waitMs,
|
|
1725
|
+
pollMs,
|
|
1726
|
+
() => tryPostJson(bridgeHealthUrl(registryEntry)),
|
|
1727
|
+
(state) => state.ok === true
|
|
1728
|
+
);
|
|
1729
|
+
});
|
|
1730
|
+
if (failures.length > 0) {
|
|
1731
|
+
stopProcessTree(browserProcess?.pid);
|
|
1732
|
+
stopProcessTree(serveWebProcess?.child?.pid);
|
|
1733
|
+
return buildStartSummary({ ok: false, reused: false, startedAt, hostId, fixture, vsix, codeCommand, hostKind, runDir, logsDir, stepsDir, metadata, registryEntry, steps, failures, orphanCleanup });
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
metadata = {
|
|
1737
|
+
hostId,
|
|
1738
|
+
kind: "serve-web-headless-client",
|
|
1739
|
+
hostKind,
|
|
1740
|
+
fixture,
|
|
1741
|
+
vsix,
|
|
1742
|
+
vsixFingerprint,
|
|
1743
|
+
codeCommand: commandDisplayName(codeCommand),
|
|
1744
|
+
codeCommandSource: codeCommand.source ?? null,
|
|
1745
|
+
runDir,
|
|
1746
|
+
logsDir,
|
|
1747
|
+
stepsDir,
|
|
1748
|
+
port,
|
|
1749
|
+
rootUrl,
|
|
1750
|
+
serveWebPid: serveWebProcess?.child?.pid ?? null,
|
|
1751
|
+
browserPid: browserProcess?.pid ?? null,
|
|
1752
|
+
instanceId: registryEntry?.instanceId ?? null,
|
|
1753
|
+
registryPath: registryEntry?.registryPath ?? null,
|
|
1754
|
+
workspaceFolders: registryEntry?.workspaceFolders ?? [],
|
|
1755
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
1756
|
+
updatedAt: new Date().toISOString()
|
|
1757
|
+
};
|
|
1758
|
+
metadata.lastActivityAt = metadata.updatedAt;
|
|
1759
|
+
metadata.ttlMs = ttlMs;
|
|
1760
|
+
metadata.watchdogIntervalMs = intervalMs;
|
|
1761
|
+
writeMetadata(hostId, metadata);
|
|
1762
|
+
startManagedServeWebWatchdog({ fixture, hostId, ttlMs, intervalMs, logsDir, ideCommand: options.ideCommand, hostKind });
|
|
1763
|
+
|
|
1764
|
+
return buildStartSummary({
|
|
1765
|
+
ok: true,
|
|
1766
|
+
reused: false,
|
|
1767
|
+
startedAt,
|
|
1768
|
+
hostId,
|
|
1769
|
+
fixture,
|
|
1770
|
+
vsix,
|
|
1771
|
+
codeCommand: commandDisplayName(codeCommand),
|
|
1772
|
+
codeCommandSource: codeCommand.source ?? null,
|
|
1773
|
+
hostKind,
|
|
1774
|
+
runDir,
|
|
1775
|
+
logsDir,
|
|
1776
|
+
stepsDir,
|
|
1777
|
+
metadata,
|
|
1567
1778
|
registryEntry,
|
|
1568
1779
|
steps,
|
|
1569
1780
|
failures,
|
|
1570
1781
|
orphanCleanup
|
|
1571
1782
|
});
|
|
1572
|
-
} finally {
|
|
1573
|
-
lock.release();
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
async function stopManagedServeWebHost(options = {}) {
|
|
1578
|
-
if (options.all === true) {
|
|
1579
|
-
return stopAllManagedServeWebHosts(options);
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
|
|
1583
|
-
const codeCommand = resolveManagedIdeCommand(options);
|
|
1783
|
+
} finally {
|
|
1784
|
+
lock.release();
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
async function stopManagedServeWebHost(options = {}) {
|
|
1789
|
+
if (options.all === true) {
|
|
1790
|
+
return stopAllManagedServeWebHosts(options);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
|
|
1794
|
+
const codeCommand = resolveManagedIdeCommand(options);
|
|
1584
1795
|
const hostKind = resolveManagedHostKind(options, codeCommand);
|
|
1585
1796
|
const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
|
|
1586
1797
|
const metadata = readMetadataByHostId(hostId);
|
|
@@ -1595,286 +1806,286 @@ async function stopManagedServeWebHost(options = {}) {
|
|
|
1595
1806
|
orphanCleanup
|
|
1596
1807
|
};
|
|
1597
1808
|
}
|
|
1598
|
-
|
|
1599
|
-
const stopResults = [];
|
|
1600
|
-
const watchdog = readJsonFile(getWatchdogPath(hostId));
|
|
1601
|
-
if (watchdog && Number.isInteger(watchdog.pid) && watchdog.pid !== process.pid) {
|
|
1602
|
-
stopResults.push({
|
|
1603
|
-
name: "watchdog",
|
|
1604
|
-
...stopProcessTree(watchdog.pid)
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
if (Number.isInteger(metadata.browserPid)) {
|
|
1608
|
-
stopResults.push({
|
|
1609
|
-
name: "headless-browser",
|
|
1610
|
-
...stopProcessTree(metadata.browserPid)
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
if (Number.isInteger(metadata.serveWebPid)) {
|
|
1614
|
-
stopResults.push({
|
|
1615
|
-
name: "serve-web",
|
|
1616
|
-
...stopProcessTree(metadata.serveWebPid)
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
|
-
if (typeof metadata.runDir === "string") {
|
|
1620
|
-
stopResults.push({
|
|
1621
|
-
name: "run-dir-processes",
|
|
1622
|
-
...stopProcessesByCommandLineFragment(metadata.runDir)
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
let registryCleanup = {
|
|
1627
|
-
removed: false,
|
|
1628
|
-
path: metadata.registryPath ?? null
|
|
1629
|
-
};
|
|
1630
|
-
if (typeof metadata.registryPath === "string") {
|
|
1631
|
-
try {
|
|
1632
|
-
fs.rmSync(metadata.registryPath, { force: true });
|
|
1633
|
-
registryCleanup = {
|
|
1634
|
-
removed: true,
|
|
1635
|
-
path: metadata.registryPath
|
|
1636
|
-
};
|
|
1637
|
-
} catch (error) {
|
|
1638
|
-
registryCleanup = {
|
|
1639
|
-
removed: false,
|
|
1640
|
-
path: metadata.registryPath,
|
|
1641
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
removeMetadata(hostId);
|
|
1647
|
-
fs.rmSync(getWatchdogPath(hostId), { force: true });
|
|
1648
|
-
return {
|
|
1649
|
-
ok: true,
|
|
1650
|
-
hostId,
|
|
1651
|
-
hostKind: metadata.hostKind ?? hostKind,
|
|
1652
|
-
stopped: true,
|
|
1653
|
-
metadata,
|
|
1654
|
-
stopResults,
|
|
1655
|
-
registryCleanup
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
async function stopAllManagedServeWebHosts(options = {}) {
|
|
1660
|
-
const entries = readAllManagedHostEntries();
|
|
1661
|
-
const stopped = [];
|
|
1662
|
-
for (const entry of entries) {
|
|
1663
|
-
const metadata = entry.metadata;
|
|
1664
|
-
const result = await stopManagedServeWebHost({
|
|
1665
|
-
...options,
|
|
1666
|
-
all: false,
|
|
1667
|
-
hostId: entry.hostId,
|
|
1668
|
-
fixture: metadata.fixture ?? defaultFixtureDir,
|
|
1669
|
-
hostKind: metadata.hostKind
|
|
1670
|
-
});
|
|
1671
|
-
stopped.push({
|
|
1672
|
-
hostId: entry.hostId,
|
|
1673
|
-
hostKind: result.hostKind ?? metadata.hostKind ?? null,
|
|
1674
|
-
fixture: metadata.fixture ?? null,
|
|
1675
|
-
runDir: metadata.runDir ?? null,
|
|
1676
|
-
stopped: result.stopped === true,
|
|
1677
|
-
reason: result.reason ?? null
|
|
1678
|
-
});
|
|
1679
|
-
}
|
|
1680
|
-
return {
|
|
1681
|
-
ok: true,
|
|
1682
|
-
all: true,
|
|
1683
|
-
count: stopped.length,
|
|
1684
|
-
stopped: stopped.filter((item) => item.stopped === true).length,
|
|
1685
|
-
runtimes: stopped
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
async function killManagedServeWebHosts(options = {}) {
|
|
1690
|
-
if (options.all !== true) {
|
|
1691
|
-
return {
|
|
1692
|
-
ok: false,
|
|
1693
|
-
code: "RUNTIME_KILL_REQUIRES_ALL",
|
|
1694
|
-
error: "runtime kill currently requires --all."
|
|
1695
|
-
};
|
|
1696
|
-
}
|
|
1697
|
-
const stopResult = await stopAllManagedServeWebHosts(options);
|
|
1698
|
-
const orphanCleanup = stopAibOwnedRuntimeProcesses();
|
|
1699
|
-
return {
|
|
1700
|
-
ok: true,
|
|
1701
|
-
all: true,
|
|
1702
|
-
stop: stopResult,
|
|
1703
|
-
killed: orphanCleanup.killed ?? 0,
|
|
1704
|
-
orphanCleanup
|
|
1705
|
-
};
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
async function cleanupExpiredManagedServeWebHosts(options = {}) {
|
|
1709
|
-
const ttlMs = readTtlMs(options.ttlMs);
|
|
1710
|
-
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
1711
|
-
return {
|
|
1712
|
-
ok: true,
|
|
1713
|
-
ttlMs,
|
|
1714
|
-
stopped: []
|
|
1715
|
-
};
|
|
1716
|
-
}
|
|
1717
|
-
if (!fs.existsSync(managedRootDir)) {
|
|
1718
|
-
return {
|
|
1719
|
-
ok: true,
|
|
1720
|
-
ttlMs,
|
|
1721
|
-
stopped: []
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
const stopped = [];
|
|
1726
|
-
const activeRunDirs = [];
|
|
1727
|
-
for (const item of fs.readdirSync(managedRootDir, { withFileTypes: true })) {
|
|
1728
|
-
if (!item.isDirectory()) {
|
|
1729
|
-
continue;
|
|
1730
|
-
}
|
|
1731
|
-
const hostId = item.name;
|
|
1732
|
-
const metadata = readMetadataByHostId(hostId);
|
|
1733
|
-
if (!metadata || !isMetadataExpired(metadata, ttlMs)) {
|
|
1734
|
-
if (metadata && typeof metadata.runDir === "string" && metadata.runDir.length > 0) {
|
|
1735
|
-
activeRunDirs.push(metadata.runDir);
|
|
1736
|
-
}
|
|
1737
|
-
continue;
|
|
1738
|
-
}
|
|
1739
|
-
const result = await stopManagedServeWebHost({ hostId, fixture: metadata.fixture ?? defaultFixtureDir });
|
|
1740
|
-
stopped.push({
|
|
1741
|
-
hostId,
|
|
1742
|
-
fixture: metadata.fixture ?? null,
|
|
1743
|
-
lastActivityAt: metadata.lastActivityAt ?? metadata.updatedAt ?? null,
|
|
1744
|
-
stopped: result.stopped === true
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
const orphanCleanup = stopOrphanedPackagedRuntimeProcesses(activeRunDirs);
|
|
1748
|
-
|
|
1749
|
-
return {
|
|
1750
|
-
ok: true,
|
|
1751
|
-
ttlMs,
|
|
1752
|
-
stopped,
|
|
1753
|
-
orphanCleanup
|
|
1754
|
-
};
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
async function runManagedServeWebWatchdog(options = {}) {
|
|
1758
|
-
const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
|
|
1759
|
-
const codeCommand = resolveManagedIdeCommand(options);
|
|
1760
|
-
const hostKind = resolveManagedHostKind(options, codeCommand);
|
|
1761
|
-
const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
|
|
1762
|
-
const ttlMs = readTtlMs(options.ttlMs);
|
|
1763
|
-
const intervalMs = Number.isFinite(Number(options.intervalMs)) && Number(options.intervalMs) > 0
|
|
1764
|
-
? Number(options.intervalMs)
|
|
1765
|
-
: 30_000;
|
|
1766
|
-
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
1767
|
-
return {
|
|
1768
|
-
ok: true,
|
|
1769
|
-
hostId,
|
|
1770
|
-
hostKind,
|
|
1771
|
-
watching: false,
|
|
1772
|
-
reason: "ttl-disabled"
|
|
1773
|
-
};
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
writeJsonAtomic(getWatchdogPath(hostId), {
|
|
1777
|
-
pid: process.pid,
|
|
1778
|
-
hostId,
|
|
1779
|
-
hostKind,
|
|
1780
|
-
fixture,
|
|
1781
|
-
ttlMs,
|
|
1782
|
-
intervalMs,
|
|
1783
|
-
startedAt: new Date().toISOString()
|
|
1784
|
-
});
|
|
1785
|
-
|
|
1786
|
-
while (true) {
|
|
1787
|
-
const metadata = readMetadataByHostId(hostId);
|
|
1788
|
-
if (!metadata) {
|
|
1789
|
-
return {
|
|
1790
|
-
ok: true,
|
|
1791
|
-
hostId,
|
|
1792
|
-
hostKind,
|
|
1793
|
-
stopped: false,
|
|
1794
|
-
reason: "metadata-not-found"
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1797
|
-
const status = await getManagedServeWebHostStatus({ fixture, hostId, vsix: metadata.vsix, hostKind: metadata.hostKind ?? hostKind });
|
|
1798
|
-
if (!status.running) {
|
|
1799
|
-
return {
|
|
1800
|
-
ok: true,
|
|
1801
|
-
hostId,
|
|
1802
|
-
hostKind: metadata.hostKind ?? hostKind,
|
|
1803
|
-
stopped: false,
|
|
1804
|
-
reason: "not-running"
|
|
1805
|
-
};
|
|
1806
|
-
}
|
|
1807
|
-
if (isMetadataExpired(metadata, ttlMs)) {
|
|
1808
|
-
const result = await stopManagedServeWebHost({ fixture, hostId });
|
|
1809
|
-
return {
|
|
1810
|
-
ok: true,
|
|
1811
|
-
hostId,
|
|
1812
|
-
hostKind: metadata.hostKind ?? hostKind,
|
|
1813
|
-
stopped: result.stopped === true,
|
|
1814
|
-
reason: "ttl-expired"
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
await sleep(intervalMs);
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
function startManagedServeWebWatchdog(options) {
|
|
1822
|
-
const ttlMs = readTtlMs(options.ttlMs);
|
|
1823
|
-
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
1824
|
-
return;
|
|
1825
|
-
}
|
|
1826
|
-
const existing = readJsonFile(getWatchdogPath(options.hostId));
|
|
1827
|
-
if (existing && isProcessAlive(existing.pid)) {
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
const stdoutPath = path.join(options.logsDir, "watchdog.stdout.txt");
|
|
1831
|
-
const stderrPath = path.join(options.logsDir, "watchdog.stderr.txt");
|
|
1832
|
-
const nodeRuntime = resolvePackagedNodeRuntime();
|
|
1833
|
-
startLoggedProcess(nodeRuntime.command, [
|
|
1834
|
-
path.join(__dirname, "manage-serve-web-host.cjs"),
|
|
1835
|
-
"watchdog",
|
|
1836
|
-
"--fixture",
|
|
1837
|
-
options.fixture,
|
|
1838
|
-
...(options.ideCommand ? ["--ide-command", options.ideCommand] : []),
|
|
1839
|
-
...(options.hostKind ? ["--host-kind", options.hostKind] : []),
|
|
1840
|
-
"--ttl-ms",
|
|
1841
|
-
String(ttlMs),
|
|
1842
|
-
"--interval-ms",
|
|
1843
|
-
String(options.intervalMs ?? 30_000)
|
|
1844
|
-
], {
|
|
1845
|
-
cwd: rootDir,
|
|
1846
|
-
detached: true,
|
|
1847
|
-
stdoutPath,
|
|
1848
|
-
stderrPath
|
|
1849
|
-
});
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
function buildStartSummary(input) {
|
|
1853
|
-
const codeCommand = input.codeCommand && typeof input.codeCommand === "object"
|
|
1854
|
-
? commandDisplayName(input.codeCommand)
|
|
1855
|
-
: input.codeCommand;
|
|
1856
|
-
const codeCommandSource = input.codeCommandSource
|
|
1857
|
-
?? (input.codeCommand && typeof input.codeCommand === "object" ? input.codeCommand.source : null);
|
|
1858
|
-
const summaryPath = input.reused
|
|
1859
|
-
? path.join(input.runDir, `reuse-${timestampForPath()}.json`)
|
|
1860
|
-
: path.join(input.runDir, "summary.json");
|
|
1861
|
-
const summary = {
|
|
1862
|
-
ok: input.ok,
|
|
1863
|
-
reused: input.reused,
|
|
1864
|
-
hostId: input.hostId,
|
|
1865
|
-
kind: "serve-web-headless-client",
|
|
1866
|
-
startedAt: new Date(input.startedAt).toISOString(),
|
|
1867
|
-
finishedAt: new Date().toISOString(),
|
|
1868
|
-
durationMs: Date.now() - input.startedAt,
|
|
1869
|
-
fixture: input.fixture,
|
|
1870
|
-
vsix: input.vsix,
|
|
1871
|
-
vsixFingerprint: input.metadata?.vsixFingerprint ?? fingerprintFile(input.vsix),
|
|
1872
|
-
codeCommand,
|
|
1873
|
-
codeCommandSource,
|
|
1874
|
-
hostKind: input.hostKind ?? input.metadata?.hostKind ?? "vscode-serve-web",
|
|
1875
|
-
runDir: input.runDir,
|
|
1876
|
-
metadata: input.metadata,
|
|
1877
|
-
registryEntry: input.registryEntry ?? null,
|
|
1809
|
+
|
|
1810
|
+
const stopResults = [];
|
|
1811
|
+
const watchdog = readJsonFile(getWatchdogPath(hostId));
|
|
1812
|
+
if (watchdog && Number.isInteger(watchdog.pid) && watchdog.pid !== process.pid) {
|
|
1813
|
+
stopResults.push({
|
|
1814
|
+
name: "watchdog",
|
|
1815
|
+
...stopProcessTree(watchdog.pid)
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
if (Number.isInteger(metadata.browserPid)) {
|
|
1819
|
+
stopResults.push({
|
|
1820
|
+
name: "headless-browser",
|
|
1821
|
+
...stopProcessTree(metadata.browserPid)
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
if (Number.isInteger(metadata.serveWebPid)) {
|
|
1825
|
+
stopResults.push({
|
|
1826
|
+
name: "serve-web",
|
|
1827
|
+
...stopProcessTree(metadata.serveWebPid)
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
if (typeof metadata.runDir === "string") {
|
|
1831
|
+
stopResults.push({
|
|
1832
|
+
name: "run-dir-processes",
|
|
1833
|
+
...stopProcessesByCommandLineFragment(metadata.runDir)
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
let registryCleanup = {
|
|
1838
|
+
removed: false,
|
|
1839
|
+
path: metadata.registryPath ?? null
|
|
1840
|
+
};
|
|
1841
|
+
if (typeof metadata.registryPath === "string") {
|
|
1842
|
+
try {
|
|
1843
|
+
fs.rmSync(metadata.registryPath, { force: true });
|
|
1844
|
+
registryCleanup = {
|
|
1845
|
+
removed: true,
|
|
1846
|
+
path: metadata.registryPath
|
|
1847
|
+
};
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
registryCleanup = {
|
|
1850
|
+
removed: false,
|
|
1851
|
+
path: metadata.registryPath,
|
|
1852
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
removeMetadata(hostId);
|
|
1858
|
+
fs.rmSync(getWatchdogPath(hostId), { force: true });
|
|
1859
|
+
return {
|
|
1860
|
+
ok: true,
|
|
1861
|
+
hostId,
|
|
1862
|
+
hostKind: metadata.hostKind ?? hostKind,
|
|
1863
|
+
stopped: true,
|
|
1864
|
+
metadata,
|
|
1865
|
+
stopResults,
|
|
1866
|
+
registryCleanup
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
async function stopAllManagedServeWebHosts(options = {}) {
|
|
1871
|
+
const entries = readAllManagedHostEntries();
|
|
1872
|
+
const stopped = [];
|
|
1873
|
+
for (const entry of entries) {
|
|
1874
|
+
const metadata = entry.metadata;
|
|
1875
|
+
const result = await stopManagedServeWebHost({
|
|
1876
|
+
...options,
|
|
1877
|
+
all: false,
|
|
1878
|
+
hostId: entry.hostId,
|
|
1879
|
+
fixture: metadata.fixture ?? defaultFixtureDir,
|
|
1880
|
+
hostKind: metadata.hostKind
|
|
1881
|
+
});
|
|
1882
|
+
stopped.push({
|
|
1883
|
+
hostId: entry.hostId,
|
|
1884
|
+
hostKind: result.hostKind ?? metadata.hostKind ?? null,
|
|
1885
|
+
fixture: metadata.fixture ?? null,
|
|
1886
|
+
runDir: metadata.runDir ?? null,
|
|
1887
|
+
stopped: result.stopped === true,
|
|
1888
|
+
reason: result.reason ?? null
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
return {
|
|
1892
|
+
ok: true,
|
|
1893
|
+
all: true,
|
|
1894
|
+
count: stopped.length,
|
|
1895
|
+
stopped: stopped.filter((item) => item.stopped === true).length,
|
|
1896
|
+
runtimes: stopped
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
async function killManagedServeWebHosts(options = {}) {
|
|
1901
|
+
if (options.all !== true) {
|
|
1902
|
+
return {
|
|
1903
|
+
ok: false,
|
|
1904
|
+
code: "RUNTIME_KILL_REQUIRES_ALL",
|
|
1905
|
+
error: "runtime kill currently requires --all."
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
const stopResult = await stopAllManagedServeWebHosts(options);
|
|
1909
|
+
const orphanCleanup = stopAibOwnedRuntimeProcesses();
|
|
1910
|
+
return {
|
|
1911
|
+
ok: true,
|
|
1912
|
+
all: true,
|
|
1913
|
+
stop: stopResult,
|
|
1914
|
+
killed: orphanCleanup.killed ?? 0,
|
|
1915
|
+
orphanCleanup
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
async function cleanupExpiredManagedServeWebHosts(options = {}) {
|
|
1920
|
+
const ttlMs = readTtlMs(options.ttlMs);
|
|
1921
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
1922
|
+
return {
|
|
1923
|
+
ok: true,
|
|
1924
|
+
ttlMs,
|
|
1925
|
+
stopped: []
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
if (!fs.existsSync(managedRootDir)) {
|
|
1929
|
+
return {
|
|
1930
|
+
ok: true,
|
|
1931
|
+
ttlMs,
|
|
1932
|
+
stopped: []
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
const stopped = [];
|
|
1937
|
+
const activeRunDirs = [];
|
|
1938
|
+
for (const item of fs.readdirSync(managedRootDir, { withFileTypes: true })) {
|
|
1939
|
+
if (!item.isDirectory()) {
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
const hostId = item.name;
|
|
1943
|
+
const metadata = readMetadataByHostId(hostId);
|
|
1944
|
+
if (!metadata || !isMetadataExpired(metadata, ttlMs)) {
|
|
1945
|
+
if (metadata && typeof metadata.runDir === "string" && metadata.runDir.length > 0) {
|
|
1946
|
+
activeRunDirs.push(metadata.runDir);
|
|
1947
|
+
}
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
const result = await stopManagedServeWebHost({ hostId, fixture: metadata.fixture ?? defaultFixtureDir });
|
|
1951
|
+
stopped.push({
|
|
1952
|
+
hostId,
|
|
1953
|
+
fixture: metadata.fixture ?? null,
|
|
1954
|
+
lastActivityAt: metadata.lastActivityAt ?? metadata.updatedAt ?? null,
|
|
1955
|
+
stopped: result.stopped === true
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
const orphanCleanup = stopOrphanedPackagedRuntimeProcesses(activeRunDirs);
|
|
1959
|
+
|
|
1960
|
+
return {
|
|
1961
|
+
ok: true,
|
|
1962
|
+
ttlMs,
|
|
1963
|
+
stopped,
|
|
1964
|
+
orphanCleanup
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
async function runManagedServeWebWatchdog(options = {}) {
|
|
1969
|
+
const fixture = path.resolve(options.fixture ?? defaultFixtureDir);
|
|
1970
|
+
const codeCommand = resolveManagedIdeCommand(options);
|
|
1971
|
+
const hostKind = resolveManagedHostKind(options, codeCommand);
|
|
1972
|
+
const hostId = options.hostId ?? buildHostId({ fixture, hostKind });
|
|
1973
|
+
const ttlMs = readTtlMs(options.ttlMs);
|
|
1974
|
+
const intervalMs = Number.isFinite(Number(options.intervalMs)) && Number(options.intervalMs) > 0
|
|
1975
|
+
? Number(options.intervalMs)
|
|
1976
|
+
: 30_000;
|
|
1977
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
1978
|
+
return {
|
|
1979
|
+
ok: true,
|
|
1980
|
+
hostId,
|
|
1981
|
+
hostKind,
|
|
1982
|
+
watching: false,
|
|
1983
|
+
reason: "ttl-disabled"
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
writeJsonAtomic(getWatchdogPath(hostId), {
|
|
1988
|
+
pid: process.pid,
|
|
1989
|
+
hostId,
|
|
1990
|
+
hostKind,
|
|
1991
|
+
fixture,
|
|
1992
|
+
ttlMs,
|
|
1993
|
+
intervalMs,
|
|
1994
|
+
startedAt: new Date().toISOString()
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
while (true) {
|
|
1998
|
+
const metadata = readMetadataByHostId(hostId);
|
|
1999
|
+
if (!metadata) {
|
|
2000
|
+
return {
|
|
2001
|
+
ok: true,
|
|
2002
|
+
hostId,
|
|
2003
|
+
hostKind,
|
|
2004
|
+
stopped: false,
|
|
2005
|
+
reason: "metadata-not-found"
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
const status = await getManagedServeWebHostStatus({ fixture, hostId, vsix: metadata.vsix, hostKind: metadata.hostKind ?? hostKind });
|
|
2009
|
+
if (!status.running) {
|
|
2010
|
+
return {
|
|
2011
|
+
ok: true,
|
|
2012
|
+
hostId,
|
|
2013
|
+
hostKind: metadata.hostKind ?? hostKind,
|
|
2014
|
+
stopped: false,
|
|
2015
|
+
reason: "not-running"
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
if (isMetadataExpired(metadata, ttlMs)) {
|
|
2019
|
+
const result = await stopManagedServeWebHost({ fixture, hostId });
|
|
2020
|
+
return {
|
|
2021
|
+
ok: true,
|
|
2022
|
+
hostId,
|
|
2023
|
+
hostKind: metadata.hostKind ?? hostKind,
|
|
2024
|
+
stopped: result.stopped === true,
|
|
2025
|
+
reason: "ttl-expired"
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
await sleep(intervalMs);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function startManagedServeWebWatchdog(options) {
|
|
2033
|
+
const ttlMs = readTtlMs(options.ttlMs);
|
|
2034
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
const existing = readJsonFile(getWatchdogPath(options.hostId));
|
|
2038
|
+
if (existing && isProcessAlive(existing.pid)) {
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
const stdoutPath = path.join(options.logsDir, "watchdog.stdout.txt");
|
|
2042
|
+
const stderrPath = path.join(options.logsDir, "watchdog.stderr.txt");
|
|
2043
|
+
const nodeRuntime = resolvePackagedNodeRuntime();
|
|
2044
|
+
startLoggedProcess(nodeRuntime.command, [
|
|
2045
|
+
path.join(__dirname, "manage-serve-web-host.cjs"),
|
|
2046
|
+
"watchdog",
|
|
2047
|
+
"--fixture",
|
|
2048
|
+
options.fixture,
|
|
2049
|
+
...(options.ideCommand ? ["--ide-command", options.ideCommand] : []),
|
|
2050
|
+
...(options.hostKind ? ["--host-kind", options.hostKind] : []),
|
|
2051
|
+
"--ttl-ms",
|
|
2052
|
+
String(ttlMs),
|
|
2053
|
+
"--interval-ms",
|
|
2054
|
+
String(options.intervalMs ?? 30_000)
|
|
2055
|
+
], {
|
|
2056
|
+
cwd: rootDir,
|
|
2057
|
+
detached: true,
|
|
2058
|
+
stdoutPath,
|
|
2059
|
+
stderrPath
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function buildStartSummary(input) {
|
|
2064
|
+
const codeCommand = input.codeCommand && typeof input.codeCommand === "object"
|
|
2065
|
+
? commandDisplayName(input.codeCommand)
|
|
2066
|
+
: input.codeCommand;
|
|
2067
|
+
const codeCommandSource = input.codeCommandSource
|
|
2068
|
+
?? (input.codeCommand && typeof input.codeCommand === "object" ? input.codeCommand.source : null);
|
|
2069
|
+
const summaryPath = input.reused
|
|
2070
|
+
? path.join(input.runDir, `reuse-${timestampForPath()}.json`)
|
|
2071
|
+
: path.join(input.runDir, "summary.json");
|
|
2072
|
+
const summary = {
|
|
2073
|
+
ok: input.ok,
|
|
2074
|
+
reused: input.reused,
|
|
2075
|
+
hostId: input.hostId,
|
|
2076
|
+
kind: "serve-web-headless-client",
|
|
2077
|
+
startedAt: new Date(input.startedAt).toISOString(),
|
|
2078
|
+
finishedAt: new Date().toISOString(),
|
|
2079
|
+
durationMs: Date.now() - input.startedAt,
|
|
2080
|
+
fixture: input.fixture,
|
|
2081
|
+
vsix: input.vsix,
|
|
2082
|
+
vsixFingerprint: input.metadata?.vsixFingerprint ?? fingerprintFile(input.vsix),
|
|
2083
|
+
codeCommand,
|
|
2084
|
+
codeCommandSource,
|
|
2085
|
+
hostKind: input.hostKind ?? input.metadata?.hostKind ?? "vscode-serve-web",
|
|
2086
|
+
runDir: input.runDir,
|
|
2087
|
+
metadata: input.metadata,
|
|
2088
|
+
registryEntry: input.registryEntry ?? null,
|
|
1878
2089
|
artifacts: {
|
|
1879
2090
|
logsDir: input.logsDir,
|
|
1880
2091
|
stepsDir: input.stepsDir,
|
|
@@ -1882,23 +2093,23 @@ function buildStartSummary(input) {
|
|
|
1882
2093
|
},
|
|
1883
2094
|
orphanCleanup: input.orphanCleanup ?? null,
|
|
1884
2095
|
steps: input.steps.map((record) => ({
|
|
1885
|
-
name: record.name,
|
|
1886
|
-
ok: record.ok,
|
|
1887
|
-
durationMs: record.durationMs,
|
|
1888
|
-
path: record.path
|
|
1889
|
-
})),
|
|
1890
|
-
failures: input.failures
|
|
1891
|
-
};
|
|
1892
|
-
writeJson(summaryPath, summary);
|
|
1893
|
-
return summary;
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
module.exports = {
|
|
1897
|
-
buildHostId,
|
|
1898
|
-
cleanupExpiredManagedServeWebHosts,
|
|
1899
|
-
getManagedServeWebHostStatus,
|
|
1900
|
-
killManagedServeWebHosts,
|
|
1901
|
-
runManagedServeWebWatchdog,
|
|
1902
|
-
startManagedServeWebHost,
|
|
1903
|
-
stopManagedServeWebHost
|
|
1904
|
-
};
|
|
2096
|
+
name: record.name,
|
|
2097
|
+
ok: record.ok,
|
|
2098
|
+
durationMs: record.durationMs,
|
|
2099
|
+
path: record.path
|
|
2100
|
+
})),
|
|
2101
|
+
failures: input.failures
|
|
2102
|
+
};
|
|
2103
|
+
writeJson(summaryPath, summary);
|
|
2104
|
+
return summary;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
module.exports = {
|
|
2108
|
+
buildHostId,
|
|
2109
|
+
cleanupExpiredManagedServeWebHosts,
|
|
2110
|
+
getManagedServeWebHostStatus,
|
|
2111
|
+
killManagedServeWebHosts,
|
|
2112
|
+
runManagedServeWebWatchdog,
|
|
2113
|
+
startManagedServeWebHost,
|
|
2114
|
+
stopManagedServeWebHost
|
|
2115
|
+
};
|