@linzumi/cli 1.0.117 → 1.0.119
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +336 -336
- package/package.json +2 -1
- package/scripts/build-editor-parity-oracle.mjs +1523 -0
- package/scripts/build-forwarding-parity-oracle.mjs +1390 -0
- package/scripts/build-mcp-parity-oracle.mjs +357 -0
- package/scripts/build-mcp-selector-parity-oracle.mjs +65 -0
- package/scripts/build-vm-sandbox-parity-oracle.mjs +1266 -0
- package/scripts/build.mjs +41 -0
|
@@ -0,0 +1,1523 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 14, editor).
|
|
3
|
+
// Relationship: Builds the TS-side PARITY ORACLE for the local-editor port
|
|
4
|
+
// of the ReScript commander. ONE esbuild bundle imports the REAL
|
|
5
|
+
// localEditor.ts + localEditorRuntime.ts exports (plus the cluster 14 seam
|
|
6
|
+
// re-exports) behind the cluster 1/2 stdin/stdout batch driver (a JSON
|
|
7
|
+
// array of cases in, a JSON array of results out). The ReScript package's
|
|
8
|
+
// EditorParityConformance drives the SAME inputs through
|
|
9
|
+
// src/editor/CommanderEditor* and asserts serialized-JSON equality; the
|
|
10
|
+
// EditorLawsConformance suite replays the named FLOW scenarios below with
|
|
11
|
+
// equivalent scripted deps and asserts the normalized flow outputs match.
|
|
12
|
+
//
|
|
13
|
+
// The two crown ops are named-scenario FLOWS:
|
|
14
|
+
// - `start_editor_flow`: run the REAL startLocalEditor against a scratch
|
|
15
|
+
// fake installed runtime and a fake code-server executable (a node
|
|
16
|
+
// script that binds the given --bind-addr port and serves HTTP - the
|
|
17
|
+
// localEditor.test.ts technique), covering every frozen refusal reason
|
|
18
|
+
// plus ready / idempotent / replace / full OCT-collaboration legs
|
|
19
|
+
// (with a REAL sidecar app.js extracted from a REAL tarball);
|
|
20
|
+
// - `resolve_runtime_flow`: run the REAL resolveEditorRuntime with a
|
|
21
|
+
// scripted fetchImpl (manifest legs for every frozen unavailable
|
|
22
|
+
// reason incl. the 401 token_expired split and checksum_mismatch) over
|
|
23
|
+
// REAL tar.gz archives built from a fixed tree spec.
|
|
24
|
+
//
|
|
25
|
+
// Determinism: ports normalize to [port], pids to [pid], ISO timestamps to
|
|
26
|
+
// [iso] (the onboarding-oracle normalization trick), scratch roots to $DIR,
|
|
27
|
+
// the mkdtemp'd profile dir to $UDD, the cache root to $CACHE, homedir to
|
|
28
|
+
// $HOME. Audit events are isolated per case through a scratch
|
|
29
|
+
// LINZUMI_CLI_AUDIT_LOG and read back normalized.
|
|
30
|
+
//
|
|
31
|
+
// SECRETS: synthetic case payloads only; .differential/ is gitignored.
|
|
32
|
+
import { mkdir } from 'node:fs/promises';
|
|
33
|
+
import { dirname, join } from 'node:path';
|
|
34
|
+
import { fileURLToPath } from 'node:url';
|
|
35
|
+
import { build } from 'esbuild';
|
|
36
|
+
|
|
37
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
38
|
+
|
|
39
|
+
const driverSource = `
|
|
40
|
+
import { spawnSync } from 'node:child_process';
|
|
41
|
+
import { createHash } from 'node:crypto';
|
|
42
|
+
import {
|
|
43
|
+
chmodSync,
|
|
44
|
+
existsSync,
|
|
45
|
+
mkdirSync,
|
|
46
|
+
mkdtempSync,
|
|
47
|
+
readdirSync,
|
|
48
|
+
readFileSync,
|
|
49
|
+
realpathSync,
|
|
50
|
+
rmSync,
|
|
51
|
+
statSync,
|
|
52
|
+
writeFileSync,
|
|
53
|
+
} from 'node:fs';
|
|
54
|
+
import { homedir, tmpdir } from 'node:os';
|
|
55
|
+
import { join, relative } from 'node:path';
|
|
56
|
+
import {
|
|
57
|
+
codeServerArgs,
|
|
58
|
+
codeServerEnv,
|
|
59
|
+
codeServerSettingsForTest,
|
|
60
|
+
localEditorBrowserBaseUrlForTest,
|
|
61
|
+
localEditorCapabilities,
|
|
62
|
+
nodeRuntimeExecutable,
|
|
63
|
+
prepareCodeServerLaunch,
|
|
64
|
+
prepareCodeServerProfile,
|
|
65
|
+
prepareLocalEditorCollaboration,
|
|
66
|
+
startLocalEditor,
|
|
67
|
+
wrappedCodeServerBinForTest,
|
|
68
|
+
} from './src/localEditor';
|
|
69
|
+
import {
|
|
70
|
+
codeServerRuntimeRootForTest,
|
|
71
|
+
defaultEditorRuntimeCacheRootForTest,
|
|
72
|
+
editorRuntimePlatform,
|
|
73
|
+
editorRuntimeStatusPayload,
|
|
74
|
+
normalizeManifestForTest,
|
|
75
|
+
resolveEditorRuntime,
|
|
76
|
+
runtimeInstallRootForTest,
|
|
77
|
+
sameOriginForTest,
|
|
78
|
+
} from './src/localEditorRuntime';
|
|
79
|
+
|
|
80
|
+
const orNull = (value) => (value === undefined ? null : value);
|
|
81
|
+
|
|
82
|
+
function outcome(run) {
|
|
83
|
+
try {
|
|
84
|
+
const value = run();
|
|
85
|
+
return value === undefined ? { defined: false } : { defined: true, value };
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
threw: true,
|
|
89
|
+
message: error instanceof Error ? error.message : String(error),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function scratch() {
|
|
95
|
+
return realpathSync(mkdtempSync(join(tmpdir(), 'linzumi-editor-oracle-')));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function substitute(value, from, to) {
|
|
99
|
+
if (typeof value === 'string') {
|
|
100
|
+
return value.split(from).join(to);
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
return value.map((entry) => substitute(entry, from, to));
|
|
104
|
+
}
|
|
105
|
+
if (typeof value === 'object' && value !== null) {
|
|
106
|
+
return Object.fromEntries(
|
|
107
|
+
Object.entries(value).map(([key, entry]) => [
|
|
108
|
+
substitute(key, from, to),
|
|
109
|
+
substitute(entry, from, to),
|
|
110
|
+
])
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const PROFILE_DIR_PATTERN = /[^"\\s]*kandan-local-editor-[^\\/"\\s]+/g;
|
|
117
|
+
const ISO_PATTERN = /\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z/g;
|
|
118
|
+
|
|
119
|
+
function normalizeStrings(value, mapString) {
|
|
120
|
+
if (typeof value === 'string') {
|
|
121
|
+
return mapString(value);
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
return value.map((entry) => normalizeStrings(entry, mapString));
|
|
125
|
+
}
|
|
126
|
+
if (typeof value === 'object' && value !== null) {
|
|
127
|
+
return Object.fromEntries(
|
|
128
|
+
Object.entries(value).map(([key, entry]) => [
|
|
129
|
+
key,
|
|
130
|
+
normalizeStrings(entry, mapString),
|
|
131
|
+
])
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- Shared fixture builders (mirrored by EditorLawsConformance.res) --------------------
|
|
138
|
+
|
|
139
|
+
function writeFileAt(path, contents) {
|
|
140
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
141
|
+
writeFileSync(path, contents);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function writeBrowserAssetInputs(runtimeRoot) {
|
|
145
|
+
writeFileAt(
|
|
146
|
+
join(runtimeRoot, 'lib', 'vscode', 'extensions', 'git-base', 'dist', 'extension.js'),
|
|
147
|
+
'git-base browser shim'
|
|
148
|
+
);
|
|
149
|
+
writeFileAt(
|
|
150
|
+
join(
|
|
151
|
+
runtimeRoot,
|
|
152
|
+
'lib',
|
|
153
|
+
'vscode',
|
|
154
|
+
'extensions',
|
|
155
|
+
'merge-conflict',
|
|
156
|
+
'dist',
|
|
157
|
+
'mergeConflictMain.js'
|
|
158
|
+
),
|
|
159
|
+
'merge-conflict browser shim'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function fakeInstalledEditorRuntime(root, codeServerBin, opts = {}) {
|
|
164
|
+
const runtimeRoot = join(root, '.fake-runtime');
|
|
165
|
+
const documentStateExtensionDir = join(
|
|
166
|
+
runtimeRoot,
|
|
167
|
+
'kandan',
|
|
168
|
+
'editor_extensions',
|
|
169
|
+
'kandan.document-state-telemetry'
|
|
170
|
+
);
|
|
171
|
+
mkdirSync(join(documentStateExtensionDir, 'out'), { recursive: true });
|
|
172
|
+
writeFileSync(
|
|
173
|
+
join(documentStateExtensionDir, 'package.json'),
|
|
174
|
+
JSON.stringify({ name: 'kandan.document-state-telemetry' })
|
|
175
|
+
);
|
|
176
|
+
writeFileSync(join(documentStateExtensionDir, 'out', 'extension.js'), '');
|
|
177
|
+
if (opts.withBrowserAssets !== false) {
|
|
178
|
+
writeBrowserAssetInputs(runtimeRoot);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
mode: 'server_managed',
|
|
182
|
+
root: runtimeRoot,
|
|
183
|
+
codeServerBin,
|
|
184
|
+
assets: {
|
|
185
|
+
collaborationExtensionTarball: join(
|
|
186
|
+
runtimeRoot,
|
|
187
|
+
'kandan',
|
|
188
|
+
'editor_extensions',
|
|
189
|
+
'typefox.open-collaboration-tools.tar.gz'
|
|
190
|
+
),
|
|
191
|
+
collaborationServerTarball: join(
|
|
192
|
+
runtimeRoot,
|
|
193
|
+
'kandan',
|
|
194
|
+
'editor_servers',
|
|
195
|
+
'open-collaboration-server.tar.gz'
|
|
196
|
+
),
|
|
197
|
+
documentStateExtensionDir,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const READY_BIN = (root) => \`#!/bin/sh
|
|
203
|
+
if [ "\\\${PORT+x}" = "x" ]; then
|
|
204
|
+
printf "PORT leaked into code-server env" >&2
|
|
205
|
+
exit 99
|
|
206
|
+
fi
|
|
207
|
+
if [ "\\\${PWD:-}" != "\${root}" ]; then
|
|
208
|
+
printf "PWD was %s" "\\\${PWD:-undefined}" >&2
|
|
209
|
+
exit 98
|
|
210
|
+
fi
|
|
211
|
+
exec node -e '
|
|
212
|
+
const http = require("node:http");
|
|
213
|
+
const bindIndex = process.argv.indexOf("--bind-addr");
|
|
214
|
+
const bindAddress = process.argv[bindIndex + 1];
|
|
215
|
+
const [hostname, portText] = bindAddress.split(":");
|
|
216
|
+
setTimeout(() => {
|
|
217
|
+
http.createServer((_request, response) => response.end("ready")).listen(Number(portText), hostname);
|
|
218
|
+
}, 120);
|
|
219
|
+
setInterval(() => {}, 1000);
|
|
220
|
+
' -- "\$@"
|
|
221
|
+
\`;
|
|
222
|
+
|
|
223
|
+
const SIMPLE_BIN = \`#!/usr/bin/env node
|
|
224
|
+
const http = require("node:http");
|
|
225
|
+
const bindIndex = process.argv.indexOf("--bind-addr");
|
|
226
|
+
const bindAddress = process.argv[bindIndex + 1];
|
|
227
|
+
const [hostname, portText] = bindAddress.split(":");
|
|
228
|
+
http.createServer((_request, response) => response.end("ready")).listen(Number(portText), hostname);
|
|
229
|
+
setInterval(() => {}, 1000);
|
|
230
|
+
\`;
|
|
231
|
+
|
|
232
|
+
const EXIT_BIN = ['#!/usr/bin/env node', 'setImmediate(() => process.exit(2));'].join('\\n');
|
|
233
|
+
|
|
234
|
+
const OCT_APP_JS = \`const http = require("node:http");
|
|
235
|
+
const portIndex = process.argv.indexOf("--port");
|
|
236
|
+
const hostIndex = process.argv.indexOf("--hostname");
|
|
237
|
+
const port = Number(process.argv[portIndex + 1]);
|
|
238
|
+
const hostname = process.argv[hostIndex + 1];
|
|
239
|
+
if (process.env.OCT_ACTIVATE_SIMPLE_LOGIN !== "true" || process.env.OCT_SERVER_OWNER !== "Kandan") {
|
|
240
|
+
process.exit(97);
|
|
241
|
+
}
|
|
242
|
+
http.createServer((_request, response) => response.end("oct")).listen(port, hostname);
|
|
243
|
+
setInterval(() => {}, 1000);
|
|
244
|
+
\`;
|
|
245
|
+
|
|
246
|
+
const OCT_DEAD_APP_JS = 'setImmediate(() => process.exit(3));\\n';
|
|
247
|
+
|
|
248
|
+
function tarDirectory(srcDir, archivePath, entries) {
|
|
249
|
+
const result = spawnSync('tar', ['-czf', archivePath, '-C', srcDir, ...entries], {
|
|
250
|
+
encoding: 'utf8',
|
|
251
|
+
});
|
|
252
|
+
if (result.status !== 0) {
|
|
253
|
+
throw new Error('tar failed: ' + (result.stderr ?? ''));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// The two collaboration tarballs the sidecar leg extracts for real.
|
|
258
|
+
function buildCollaborationTarballs(root, runtime, appJs) {
|
|
259
|
+
const stage = join(root, '.tarball-stage');
|
|
260
|
+
writeFileAt(join(stage, 'extension', 'extension-marker.txt'), 'oct extension payload');
|
|
261
|
+
mkdirSync(join(runtime.root, 'kandan', 'editor_extensions'), { recursive: true });
|
|
262
|
+
mkdirSync(join(runtime.root, 'kandan', 'editor_servers'), { recursive: true });
|
|
263
|
+
tarDirectory(join(stage, 'extension'), runtime.assets.collaborationExtensionTarball, ['.']);
|
|
264
|
+
writeFileAt(
|
|
265
|
+
join(stage, 'server', 'open-collaboration-server', 'bundle', 'app.js'),
|
|
266
|
+
appJs
|
|
267
|
+
);
|
|
268
|
+
tarDirectory(join(stage, 'server'), runtime.assets.collaborationServerTarball, ['.']);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function writeExecutable(path, contents) {
|
|
272
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
273
|
+
writeFileSync(path, contents);
|
|
274
|
+
chmodSync(path, 0o755);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const COLLAB_CONTROL = {
|
|
278
|
+
provider: 'oct',
|
|
279
|
+
editorSessionId: 42,
|
|
280
|
+
runtimeSessionId: 'local-runtime-42',
|
|
281
|
+
roomId: 'octroom42',
|
|
282
|
+
extensionAssetPath:
|
|
283
|
+
'/api/v2/editor/runtime-assets/extensions/typefox.open-collaboration-tools.tar.gz',
|
|
284
|
+
serverAssetPath:
|
|
285
|
+
'/api/v2/editor/runtime-assets/servers/open-collaboration-server.tar.gz',
|
|
286
|
+
bootstrapToken: 'signed.bootstrap',
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// --- Audit capture ------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
function caseAuditLog(dir) {
|
|
292
|
+
const logFile = join(dir, 'audit.jsonl');
|
|
293
|
+
process.env.LINZUMI_CLI_AUDIT_LOG = logFile;
|
|
294
|
+
return logFile;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function readAuditEvents(logFile) {
|
|
298
|
+
if (!existsSync(logFile)) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
return readFileSync(logFile, 'utf8')
|
|
302
|
+
.split('\\n')
|
|
303
|
+
.filter((line) => line.trim() !== '')
|
|
304
|
+
.map((line) => JSON.parse(line));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// --- start_editor_flow ---------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
const LOOPBACK_PORT_PATTERN = /127\\.0\\.0\\.1:\\d+/g;
|
|
310
|
+
const FORWARDS_PORT_PATTERN = /\\/forwards\\/\\d+\\//g;
|
|
311
|
+
|
|
312
|
+
function normalizeFlowValue(value, dir) {
|
|
313
|
+
let normalized = substitute(value, dir, '$DIR');
|
|
314
|
+
normalized = normalizeStrings(normalized, (text) =>
|
|
315
|
+
text
|
|
316
|
+
.replace(PROFILE_DIR_PATTERN, '$UDD')
|
|
317
|
+
.replace(ISO_PATTERN, '[iso]')
|
|
318
|
+
.replace(LOOPBACK_PORT_PATTERN, '127.0.0.1:[port]')
|
|
319
|
+
.replace(FORWARDS_PORT_PATTERN, '/forwards/[port]/')
|
|
320
|
+
);
|
|
321
|
+
return normalized;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function normalizeAuditEvents(events, dir) {
|
|
325
|
+
return events.map((entry) => {
|
|
326
|
+
const mapped = { ...entry };
|
|
327
|
+
mapped.ts = '[iso]';
|
|
328
|
+
if (typeof mapped.pid === 'number') {
|
|
329
|
+
mapped.pid = '[pid]';
|
|
330
|
+
}
|
|
331
|
+
if (Array.isArray(mapped.args)) {
|
|
332
|
+
mapped.args = mapped.args.map((arg, index) =>
|
|
333
|
+
index > 0 && mapped.args[index - 1] === '--port' ? '[port]' : arg
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
return normalizeFlowValue(mapped, dir);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function eventProjection(event) {
|
|
341
|
+
return {
|
|
342
|
+
cwd: event.cwd,
|
|
343
|
+
portBound: event.port > 0,
|
|
344
|
+
startedAt: event.startedAt,
|
|
345
|
+
collaboration:
|
|
346
|
+
event.collaboration === undefined
|
|
347
|
+
? null
|
|
348
|
+
: {
|
|
349
|
+
provider: event.collaboration.provider,
|
|
350
|
+
editorSessionId: event.collaboration.editorSessionId,
|
|
351
|
+
runtimeSessionId: event.collaboration.runtimeSessionId,
|
|
352
|
+
roomId: event.collaboration.roomId,
|
|
353
|
+
serverPortBound: event.collaboration.serverPort > 0,
|
|
354
|
+
serverUrl: event.collaboration.serverUrl,
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function capabilitiesProjection(runtime, state) {
|
|
360
|
+
const capabilities = localEditorCapabilities(runtime, state.status === 'running' ? [state.cwd] : ['/tmp'], state);
|
|
361
|
+
const mapped = { ...capabilities };
|
|
362
|
+
if (typeof mapped.localEditorPort === 'number') {
|
|
363
|
+
mapped.localEditorPort = '[port]';
|
|
364
|
+
}
|
|
365
|
+
if (mapped.localEditorCollaboration !== undefined) {
|
|
366
|
+
mapped.localEditorCollaboration = {
|
|
367
|
+
...mapped.localEditorCollaboration,
|
|
368
|
+
serverPort: '[port]',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
return mapped;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function settingsBytesFromAudit(events) {
|
|
375
|
+
for (const entry of events) {
|
|
376
|
+
if (entry.event === 'process.spawn' && entry.purpose === 'local_editor.code_server') {
|
|
377
|
+
const index = entry.args.indexOf('--user-data-dir');
|
|
378
|
+
if (index >= 0) {
|
|
379
|
+
const userDataDir = entry.args[index + 1];
|
|
380
|
+
const settingsPath = join(userDataDir, 'User', 'settings.json');
|
|
381
|
+
if (existsSync(settingsPath)) {
|
|
382
|
+
return readFileSync(settingsPath, 'utf8');
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Kill a started editor state and WAIT for its exits (bounded): the audit
|
|
391
|
+
// log path is process-global per case, so a straggler process.exit event
|
|
392
|
+
// from case N must never land in case N+1's log.
|
|
393
|
+
async function killStateAndWait(state) {
|
|
394
|
+
if (state !== undefined && state.status === 'running') {
|
|
395
|
+
const bounded = (exited) =>
|
|
396
|
+
Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 3000))]);
|
|
397
|
+
const waits = [bounded(state.exited)];
|
|
398
|
+
try {
|
|
399
|
+
state.process.kill('SIGINT');
|
|
400
|
+
} catch (_error) {}
|
|
401
|
+
if (state.collaboration !== undefined) {
|
|
402
|
+
try {
|
|
403
|
+
state.collaboration.process.kill('SIGINT');
|
|
404
|
+
} catch (_error) {}
|
|
405
|
+
waits.push(bounded(state.collaboration.exited));
|
|
406
|
+
}
|
|
407
|
+
await Promise.all(waits);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function fetchBody(port) {
|
|
412
|
+
try {
|
|
413
|
+
const response = await fetch('http://127.0.0.1:' + port + '/');
|
|
414
|
+
return await response.text();
|
|
415
|
+
} catch (error) {
|
|
416
|
+
return 'fetch failed: ' + (error instanceof Error ? error.message : String(error));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function handleStartEditorFlow(testCase) {
|
|
421
|
+
const dir = scratch();
|
|
422
|
+
const logFile = caseAuditLog(dir);
|
|
423
|
+
const previousPort = process.env.PORT;
|
|
424
|
+
process.env.PORT = '4140';
|
|
425
|
+
const ports = [];
|
|
426
|
+
const startedStates = [];
|
|
427
|
+
const record = { scenario: testCase.scenario };
|
|
428
|
+
try {
|
|
429
|
+
const run = async (control, options) => {
|
|
430
|
+
const result = await startLocalEditor(control, options);
|
|
431
|
+
if (result.ok) {
|
|
432
|
+
ports.push(result.event.port);
|
|
433
|
+
if (result.state.status === 'running' && result.state.collaboration !== undefined) {
|
|
434
|
+
ports.push(result.state.collaboration.serverPort);
|
|
435
|
+
}
|
|
436
|
+
startedStates.push(result.state);
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
};
|
|
440
|
+
const project = (result) => ({
|
|
441
|
+
ok: result.ok,
|
|
442
|
+
reason: result.ok ? null : result.reason,
|
|
443
|
+
event: result.ok ? eventProjection(result.event) : null,
|
|
444
|
+
stateStatus: result.state.status,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
switch (testCase.scenario) {
|
|
448
|
+
case 'disabled': {
|
|
449
|
+
const result = await run(
|
|
450
|
+
{ type: 'start_local_editor', cwd: dir },
|
|
451
|
+
{
|
|
452
|
+
codeServerBin: undefined,
|
|
453
|
+
editorRuntime: undefined,
|
|
454
|
+
allowedCwds: [dir],
|
|
455
|
+
currentState: { status: 'disabled' },
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
record.result = project(result);
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case 'missing_cwd':
|
|
462
|
+
case 'no_allowed_cwd':
|
|
463
|
+
case 'cwd_not_allowed':
|
|
464
|
+
case 'cwd_not_found':
|
|
465
|
+
case 'cwd_dangerous': {
|
|
466
|
+
const root = join(dir, 'root');
|
|
467
|
+
const outside = join(dir, 'outside');
|
|
468
|
+
mkdirSync(root, { recursive: true });
|
|
469
|
+
mkdirSync(outside, { recursive: true });
|
|
470
|
+
const runtime = fakeInstalledEditorRuntime(root, 'code-server');
|
|
471
|
+
const byScenario = {
|
|
472
|
+
missing_cwd: { cwd: ' ', allowedCwds: [root] },
|
|
473
|
+
no_allowed_cwd: { cwd: root, allowedCwds: [] },
|
|
474
|
+
cwd_not_allowed: { cwd: outside, allowedCwds: [root] },
|
|
475
|
+
cwd_not_found: { cwd: join(root, 'missing-child'), allowedCwds: [root] },
|
|
476
|
+
cwd_dangerous: { cwd: '/usr/bin', allowedCwds: ['/usr/bin'] },
|
|
477
|
+
};
|
|
478
|
+
const spec = byScenario[testCase.scenario];
|
|
479
|
+
const result = await run(
|
|
480
|
+
{ type: 'start_local_editor', cwd: spec.cwd },
|
|
481
|
+
{
|
|
482
|
+
codeServerBin: 'code-server',
|
|
483
|
+
editorRuntime: runtime,
|
|
484
|
+
allowedCwds: spec.allowedCwds,
|
|
485
|
+
currentState: { status: 'disabled' },
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
record.result = project(result);
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case 'sandbox_unavailable': {
|
|
492
|
+
const root = join(dir, 'root');
|
|
493
|
+
mkdirSync(root, { recursive: true });
|
|
494
|
+
const bin = join(root, 'code-server');
|
|
495
|
+
writeExecutable(bin, SIMPLE_BIN);
|
|
496
|
+
const runtime = fakeInstalledEditorRuntime(root, bin);
|
|
497
|
+
const result = await run(
|
|
498
|
+
{ type: 'start_local_editor', cwd: root },
|
|
499
|
+
{
|
|
500
|
+
codeServerBin: bin,
|
|
501
|
+
editorRuntime: runtime,
|
|
502
|
+
allowedCwds: [root],
|
|
503
|
+
currentState: { status: 'disabled' },
|
|
504
|
+
platform: 'freebsd',
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
record.result = project(result);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
case 'missing_browser_base_url': {
|
|
511
|
+
const root = join(dir, 'root');
|
|
512
|
+
mkdirSync(root, { recursive: true });
|
|
513
|
+
const runtime = fakeInstalledEditorRuntime(root, 'code-server');
|
|
514
|
+
const result = await run(
|
|
515
|
+
{
|
|
516
|
+
type: 'start_local_editor',
|
|
517
|
+
cwd: root,
|
|
518
|
+
collaboration: { ...COLLAB_CONTROL, bootstrapToken: undefined },
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
codeServerBin: 'code-server',
|
|
522
|
+
editorRuntime: runtime,
|
|
523
|
+
allowedCwds: [root],
|
|
524
|
+
currentState: { status: 'disabled' },
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
record.result = project(result);
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case 'spawn_failed': {
|
|
531
|
+
const root = join(dir, 'root');
|
|
532
|
+
mkdirSync(root, { recursive: true });
|
|
533
|
+
const missingBin = join(root, 'missing-code-server');
|
|
534
|
+
const runtime = fakeInstalledEditorRuntime(root, missingBin);
|
|
535
|
+
const result = await run(
|
|
536
|
+
{ type: 'start_local_editor', cwd: root },
|
|
537
|
+
{
|
|
538
|
+
codeServerBin: missingBin,
|
|
539
|
+
editorRuntime: runtime,
|
|
540
|
+
allowedCwds: [root],
|
|
541
|
+
currentState: { status: 'disabled' },
|
|
542
|
+
platform: 'darwin',
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
record.result = project(result);
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case 'exit_before_ready': {
|
|
549
|
+
const root = join(dir, 'root');
|
|
550
|
+
mkdirSync(root, { recursive: true });
|
|
551
|
+
const bin = join(root, 'code-server');
|
|
552
|
+
writeExecutable(bin, EXIT_BIN);
|
|
553
|
+
const runtime = fakeInstalledEditorRuntime(root, bin);
|
|
554
|
+
const result = await run(
|
|
555
|
+
{ type: 'start_local_editor', cwd: root },
|
|
556
|
+
{
|
|
557
|
+
codeServerBin: bin,
|
|
558
|
+
editorRuntime: runtime,
|
|
559
|
+
allowedCwds: [root],
|
|
560
|
+
currentState: { status: 'disabled' },
|
|
561
|
+
platform: 'darwin',
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
record.result = project(result);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case 'ready':
|
|
568
|
+
case 'ready_idempotent': {
|
|
569
|
+
const root = join(dir, 'root');
|
|
570
|
+
mkdirSync(root, { recursive: true });
|
|
571
|
+
const bin = join(root, 'code-server');
|
|
572
|
+
writeExecutable(bin, READY_BIN(root));
|
|
573
|
+
const runtime = fakeInstalledEditorRuntime(root, bin);
|
|
574
|
+
const options = {
|
|
575
|
+
codeServerBin: bin,
|
|
576
|
+
editorRuntime: runtime,
|
|
577
|
+
allowedCwds: [root],
|
|
578
|
+
currentState: { status: 'disabled' },
|
|
579
|
+
platform: 'darwin',
|
|
580
|
+
};
|
|
581
|
+
const result = await run({ type: 'start_local_editor', cwd: root }, options);
|
|
582
|
+
record.result = project(result);
|
|
583
|
+
if (result.ok) {
|
|
584
|
+
record.readyBody = await fetchBody(result.event.port);
|
|
585
|
+
record.capabilities = capabilitiesProjection(runtime, result.state);
|
|
586
|
+
if (testCase.scenario === 'ready_idempotent') {
|
|
587
|
+
const second = await run(
|
|
588
|
+
{ type: 'start_local_editor', cwd: root },
|
|
589
|
+
{ ...options, currentState: result.state }
|
|
590
|
+
);
|
|
591
|
+
record.second = project(second);
|
|
592
|
+
record.samePort = second.ok && second.event.port === result.event.port;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
case 'ready_replace': {
|
|
598
|
+
const cwdA = join(dir, 'project-a');
|
|
599
|
+
const cwdB = join(dir, 'project-b');
|
|
600
|
+
mkdirSync(cwdA, { recursive: true });
|
|
601
|
+
mkdirSync(cwdB, { recursive: true });
|
|
602
|
+
const bin = join(dir, 'code-server');
|
|
603
|
+
writeExecutable(bin, SIMPLE_BIN);
|
|
604
|
+
const runtime = fakeInstalledEditorRuntime(dir, bin);
|
|
605
|
+
const options = {
|
|
606
|
+
codeServerBin: bin,
|
|
607
|
+
editorRuntime: runtime,
|
|
608
|
+
allowedCwds: [dir],
|
|
609
|
+
currentState: { status: 'disabled' },
|
|
610
|
+
platform: 'darwin',
|
|
611
|
+
};
|
|
612
|
+
const first = await run({ type: 'start_local_editor', cwd: cwdA }, options);
|
|
613
|
+
record.result = project(first);
|
|
614
|
+
if (first.ok) {
|
|
615
|
+
const second = await run(
|
|
616
|
+
{ type: 'start_local_editor', cwd: cwdB },
|
|
617
|
+
{ ...options, currentState: first.state }
|
|
618
|
+
);
|
|
619
|
+
record.second = project(second);
|
|
620
|
+
record.newPort = second.ok && second.event.port !== first.event.port;
|
|
621
|
+
const firstExited = await Promise.race([
|
|
622
|
+
first.state.exited.then(() => true),
|
|
623
|
+
new Promise((resolve) => setTimeout(() => resolve(false), 5000)),
|
|
624
|
+
]);
|
|
625
|
+
record.firstExited = firstExited;
|
|
626
|
+
}
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
case 'collaboration':
|
|
630
|
+
case 'collaboration_sidecar_dies': {
|
|
631
|
+
const root = join(dir, 'root');
|
|
632
|
+
mkdirSync(root, { recursive: true });
|
|
633
|
+
const bin = join(root, 'code-server');
|
|
634
|
+
writeExecutable(bin, READY_BIN(root));
|
|
635
|
+
const runtime = fakeInstalledEditorRuntime(root, bin);
|
|
636
|
+
buildCollaborationTarballs(
|
|
637
|
+
root,
|
|
638
|
+
runtime,
|
|
639
|
+
testCase.scenario === 'collaboration' ? OCT_APP_JS : OCT_DEAD_APP_JS
|
|
640
|
+
);
|
|
641
|
+
const result = await run(
|
|
642
|
+
{
|
|
643
|
+
type: 'start_local_editor',
|
|
644
|
+
cwd: root,
|
|
645
|
+
collaboration: COLLAB_CONTROL,
|
|
646
|
+
browserBaseUrl: 'https://linzumi.io:4140',
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
codeServerBin: bin,
|
|
650
|
+
editorRuntime: runtime,
|
|
651
|
+
allowedCwds: [root],
|
|
652
|
+
currentState: { status: 'disabled' },
|
|
653
|
+
platform: 'darwin',
|
|
654
|
+
runnerId: 'runner-a',
|
|
655
|
+
}
|
|
656
|
+
);
|
|
657
|
+
record.result = project(result);
|
|
658
|
+
if (result.ok) {
|
|
659
|
+
record.capabilities = capabilitiesProjection(runtime, result.state);
|
|
660
|
+
}
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
default:
|
|
664
|
+
return { threw: true, message: 'unknown start scenario ' + String(testCase.scenario) };
|
|
665
|
+
}
|
|
666
|
+
if (testCase.scenario === 'collaboration_sidecar_dies') {
|
|
667
|
+
// The internal SIGINT kill of the code-server child lands its
|
|
668
|
+
// process.exit audit asynchronously; settle before reading.
|
|
669
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
670
|
+
}
|
|
671
|
+
const auditEvents = readAuditEvents(logFile);
|
|
672
|
+
record.settingsBytes = settingsBytesFromAudit(auditEvents);
|
|
673
|
+
const normalizedAudit = normalizeAuditEvents(auditEvents, dir);
|
|
674
|
+
// The two parallel tarball installs race their process.exit entries:
|
|
675
|
+
// the collaboration scenarios compare the audit trail as a SORTED
|
|
676
|
+
// multiset of serialized entries; every other scenario stays ordered.
|
|
677
|
+
record.auditEvents =
|
|
678
|
+
testCase.scenario === 'collaboration' ||
|
|
679
|
+
testCase.scenario === 'collaboration_sidecar_dies'
|
|
680
|
+
? normalizedAudit.map((entry) => JSON.stringify(entry)).sort()
|
|
681
|
+
: normalizedAudit;
|
|
682
|
+
return normalizeFlowValue(record, dir);
|
|
683
|
+
} finally {
|
|
684
|
+
for (const state of startedStates) {
|
|
685
|
+
await killStateAndWait(state);
|
|
686
|
+
}
|
|
687
|
+
if (previousPort === undefined) {
|
|
688
|
+
delete process.env.PORT;
|
|
689
|
+
} else {
|
|
690
|
+
process.env.PORT = previousPort;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// --- resolve_runtime_flow --------------------------------------------------------------------
|
|
696
|
+
|
|
697
|
+
function sha256(bytes) {
|
|
698
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const RUNTIME_TREE_FILES = [
|
|
702
|
+
['linzumi-editor-runtime.json', JSON.stringify({ version: '2026.04.29.9', platform: 'darwin-arm64' })],
|
|
703
|
+
['bin/code-server', '#!/bin/sh\\necho code-server\\n'],
|
|
704
|
+
['lib/vscode/node_modules/vsda/rust/web/vsda.js', 'export default {};'],
|
|
705
|
+
['lib/vscode/node_modules/vsda/rust/web/vsda_bg.wasm', 'wasm'],
|
|
706
|
+
['kandan/editor_extensions/typefox.open-collaboration-tools.tar.gz', 'oct extension tarball'],
|
|
707
|
+
['kandan/editor_servers/open-collaboration-server.tar.gz', 'oct server tarball'],
|
|
708
|
+
[
|
|
709
|
+
'kandan/editor_extensions/kandan.document-state-telemetry/package.json',
|
|
710
|
+
JSON.stringify({ name: 'kandan.document-state-telemetry' }),
|
|
711
|
+
],
|
|
712
|
+
['kandan/editor_extensions/kandan.document-state-telemetry/out/extension.js', 'module.exports = {};'],
|
|
713
|
+
];
|
|
714
|
+
|
|
715
|
+
const RUNTIME_ASSET_PATHS = [
|
|
716
|
+
'bin/code-server',
|
|
717
|
+
'lib/vscode/node_modules/vsda/rust/web/vsda.js',
|
|
718
|
+
'lib/vscode/node_modules/vsda/rust/web/vsda_bg.wasm',
|
|
719
|
+
'kandan/editor_extensions/typefox.open-collaboration-tools.tar.gz',
|
|
720
|
+
'kandan/editor_servers/open-collaboration-server.tar.gz',
|
|
721
|
+
'kandan/editor_extensions/kandan.document-state-telemetry/package.json',
|
|
722
|
+
'kandan/editor_extensions/kandan.document-state-telemetry/out/extension.js',
|
|
723
|
+
];
|
|
724
|
+
|
|
725
|
+
function buildStandardArchive(root) {
|
|
726
|
+
const src = join(root, 'runtime-src');
|
|
727
|
+
for (const [path, contents] of RUNTIME_TREE_FILES) {
|
|
728
|
+
writeFileAt(join(src, path), contents);
|
|
729
|
+
}
|
|
730
|
+
chmodSync(join(src, 'bin', 'code-server'), 0o755);
|
|
731
|
+
const archivePath = join(root, 'runtime-archive.tar.gz');
|
|
732
|
+
tarDirectory(src, archivePath, ['.']);
|
|
733
|
+
const assets = RUNTIME_ASSET_PATHS.map((path) => ({
|
|
734
|
+
path,
|
|
735
|
+
sha256: sha256(readFileSync(join(src, path))),
|
|
736
|
+
}));
|
|
737
|
+
return { path: archivePath, sha256: sha256(readFileSync(archivePath)), assets };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function buildUpstreamArchive(root, packageRoot) {
|
|
741
|
+
const src = join(root, 'upstream-src');
|
|
742
|
+
writeFileAt(join(src, packageRoot, 'bin', 'code-server'), '#!/bin/sh\\necho code-server\\n');
|
|
743
|
+
chmodSync(join(src, packageRoot, 'bin', 'code-server'), 0o755);
|
|
744
|
+
const archivePath = join(root, packageRoot + '.tar.gz');
|
|
745
|
+
tarDirectory(src, archivePath, [packageRoot]);
|
|
746
|
+
return { path: archivePath, sha256: sha256(readFileSync(archivePath)) };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function standardManifest(archive) {
|
|
750
|
+
return {
|
|
751
|
+
version: '2026.04.29.9',
|
|
752
|
+
platform: 'darwin-arm64',
|
|
753
|
+
archiveUrl: '/runtime/archive',
|
|
754
|
+
archiveSha256: archive.sha256,
|
|
755
|
+
codeServerVersion: '4.112.0',
|
|
756
|
+
codeServerBinPath: 'bin/code-server',
|
|
757
|
+
manifestPath: 'linzumi-editor-runtime.json',
|
|
758
|
+
assets: archive.assets,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function listTreeFiles(root) {
|
|
763
|
+
const files = [];
|
|
764
|
+
const walk = (dir) => {
|
|
765
|
+
for (const entry of readdirSync(dir).sort()) {
|
|
766
|
+
const full = join(dir, entry);
|
|
767
|
+
if (statSync(full).isDirectory()) {
|
|
768
|
+
walk(full);
|
|
769
|
+
} else {
|
|
770
|
+
files.push(relative(root, full));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
if (existsSync(root)) {
|
|
775
|
+
walk(root);
|
|
776
|
+
}
|
|
777
|
+
return files.sort();
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function runtimeResultProjection(result, cacheRoot) {
|
|
781
|
+
return substitute(
|
|
782
|
+
{
|
|
783
|
+
statusPayload: editorRuntimeStatusPayload(result.status),
|
|
784
|
+
codeServerBin: orNull(result.codeServerBin),
|
|
785
|
+
runtime:
|
|
786
|
+
result.runtime === undefined
|
|
787
|
+
? null
|
|
788
|
+
: {
|
|
789
|
+
root: result.runtime.root,
|
|
790
|
+
codeServerBin: result.runtime.codeServerBin,
|
|
791
|
+
assets: result.runtime.assets,
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
cacheRoot,
|
|
795
|
+
'$CACHE'
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async function handleResolveRuntimeFlow(testCase) {
|
|
800
|
+
const dir = scratch();
|
|
801
|
+
caseAuditLog(dir);
|
|
802
|
+
const cacheRoot = join(dir, 'cache');
|
|
803
|
+
const counters = { manifestFetches: 0, archiveFetches: 0 };
|
|
804
|
+
let authHeaderOnArchive = null;
|
|
805
|
+
// tar.gz bytes carry timestamps, so the archive sha256 differs per
|
|
806
|
+
// driver run: normalize it to $SHA everywhere it surfaces (status
|
|
807
|
+
// payload, install paths, the installed manifest bytes).
|
|
808
|
+
let archiveShaToNormalize = null;
|
|
809
|
+
const record = { scenario: testCase.scenario };
|
|
810
|
+
const baseOptions = {
|
|
811
|
+
kandanUrl: 'wss://linzumi.io:4140',
|
|
812
|
+
token: 'synthetic-token',
|
|
813
|
+
cacheRoot,
|
|
814
|
+
platform: 'darwin',
|
|
815
|
+
arch: 'arm64',
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const resolveWith = (overrides) => resolveEditorRuntime({ ...baseOptions, ...overrides });
|
|
819
|
+
|
|
820
|
+
switch (testCase.scenario) {
|
|
821
|
+
case 'runtime_custom': {
|
|
822
|
+
const result = await resolveWith({
|
|
823
|
+
customCodeServerBin: '/opt/homebrew/bin/code-server',
|
|
824
|
+
platform: 'freebsd',
|
|
825
|
+
});
|
|
826
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
case 'runtime_unsupported_platform': {
|
|
830
|
+
const result = await resolveWith({ platform: 'freebsd', arch: 'x64' });
|
|
831
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
case 'runtime_manifest_reject': {
|
|
835
|
+
const result = await resolveWith({
|
|
836
|
+
fetchImpl: async () => {
|
|
837
|
+
throw new Error('scripted network failure');
|
|
838
|
+
},
|
|
839
|
+
});
|
|
840
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
case 'runtime_manifest_500': {
|
|
844
|
+
const result = await resolveWith({
|
|
845
|
+
fetchImpl: async () => new Response('boom', { status: 500 }),
|
|
846
|
+
});
|
|
847
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
850
|
+
case 'runtime_auth_expired': {
|
|
851
|
+
const result = await resolveWith({
|
|
852
|
+
fetchImpl: async () =>
|
|
853
|
+
Response.json({ ok: false, error: 'token_expired' }, { status: 401 }),
|
|
854
|
+
});
|
|
855
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
case 'runtime_auth_unauthorized': {
|
|
859
|
+
const result = await resolveWith({
|
|
860
|
+
fetchImpl: async () =>
|
|
861
|
+
Response.json({ ok: false, error: 'unauthorized' }, { status: 401 }),
|
|
862
|
+
});
|
|
863
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
case 'runtime_manifest_invalid': {
|
|
867
|
+
const result = await resolveWith({
|
|
868
|
+
fetchImpl: async () =>
|
|
869
|
+
Response.json({
|
|
870
|
+
ok: true,
|
|
871
|
+
runtime: { platform: 'darwin-arm64', archiveUrl: '/runtime/archive' },
|
|
872
|
+
}),
|
|
873
|
+
});
|
|
874
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
case 'runtime_manifest_not_ok': {
|
|
878
|
+
const result = await resolveWith({
|
|
879
|
+
fetchImpl: async () => Response.json({ ok: false }),
|
|
880
|
+
});
|
|
881
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
case 'runtime_download_404': {
|
|
885
|
+
const archive = buildStandardArchive(dir);
|
|
886
|
+
archiveShaToNormalize = archive.sha256;
|
|
887
|
+
const manifest = standardManifest(archive);
|
|
888
|
+
const result = await resolveWith({
|
|
889
|
+
fetchImpl: async (input) => {
|
|
890
|
+
const url = input.toString();
|
|
891
|
+
if (url.includes('/manifest')) {
|
|
892
|
+
counters.manifestFetches += 1;
|
|
893
|
+
return Response.json({ ok: true, runtime: manifest });
|
|
894
|
+
}
|
|
895
|
+
return new Response('not found', { status: 404 });
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
case 'runtime_checksum_mismatch': {
|
|
902
|
+
const manifest = {
|
|
903
|
+
version: '2026.04.29.9',
|
|
904
|
+
platform: 'darwin-arm64',
|
|
905
|
+
archiveUrl: '/runtime/archive',
|
|
906
|
+
archiveSha256: '0'.repeat(64),
|
|
907
|
+
codeServerVersion: '4.112.0',
|
|
908
|
+
codeServerBinPath: 'bin/code-server',
|
|
909
|
+
manifestPath: 'linzumi-editor-runtime.json',
|
|
910
|
+
assets: [],
|
|
911
|
+
};
|
|
912
|
+
const result = await resolveWith({
|
|
913
|
+
fetchImpl: async (input) =>
|
|
914
|
+
input.toString().includes('/manifest')
|
|
915
|
+
? Response.json({ ok: true, runtime: manifest })
|
|
916
|
+
: new Response('not the approved archive', { status: 200 }),
|
|
917
|
+
});
|
|
918
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
case 'runtime_extract_failed': {
|
|
922
|
+
const archive = buildStandardArchive(dir);
|
|
923
|
+
archiveShaToNormalize = archive.sha256;
|
|
924
|
+
const manifest = standardManifest(archive);
|
|
925
|
+
const result = await resolveWith({
|
|
926
|
+
fetchImpl: async (input) => {
|
|
927
|
+
const url = input.toString();
|
|
928
|
+
if (url.includes('/manifest')) {
|
|
929
|
+
return Response.json({ ok: true, runtime: manifest });
|
|
930
|
+
}
|
|
931
|
+
counters.archiveFetches += 1;
|
|
932
|
+
return new Response(readFileSync(archive.path), { status: 200 });
|
|
933
|
+
},
|
|
934
|
+
extractArchive: async () => false,
|
|
935
|
+
});
|
|
936
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
case 'runtime_invalid_archive': {
|
|
940
|
+
const archive = buildStandardArchive(dir);
|
|
941
|
+
archiveShaToNormalize = archive.sha256;
|
|
942
|
+
const manifest = {
|
|
943
|
+
...standardManifest(archive),
|
|
944
|
+
version: '2026.04.29.10',
|
|
945
|
+
assets: archive.assets.filter(
|
|
946
|
+
(asset) =>
|
|
947
|
+
asset.path !==
|
|
948
|
+
'kandan/editor_extensions/typefox.open-collaboration-tools.tar.gz'
|
|
949
|
+
),
|
|
950
|
+
};
|
|
951
|
+
const result = await resolveWith({
|
|
952
|
+
fetchImpl: async (input) =>
|
|
953
|
+
input.toString().includes('/manifest')
|
|
954
|
+
? Response.json({ ok: true, runtime: manifest })
|
|
955
|
+
: new Response(readFileSync(archive.path), { status: 200 }),
|
|
956
|
+
});
|
|
957
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
case 'runtime_install_failed': {
|
|
961
|
+
const archive = buildStandardArchive(dir);
|
|
962
|
+
archiveShaToNormalize = archive.sha256;
|
|
963
|
+
const manifest = standardManifest(archive);
|
|
964
|
+
manifest.assets = [
|
|
965
|
+
...manifest.assets,
|
|
966
|
+
{ path: 'kandan/editor_extensions/extra.bin', sha256: 'a'.repeat(64), url: '/asset/boom' },
|
|
967
|
+
];
|
|
968
|
+
const result = await resolveWith({
|
|
969
|
+
fetchImpl: async (input) => {
|
|
970
|
+
const url = input.toString();
|
|
971
|
+
if (url.includes('/manifest')) {
|
|
972
|
+
return Response.json({ ok: true, runtime: manifest });
|
|
973
|
+
}
|
|
974
|
+
if (url.includes('/runtime/archive')) {
|
|
975
|
+
return new Response(readFileSync(archive.path), { status: 200 });
|
|
976
|
+
}
|
|
977
|
+
throw new Error('scripted asset failure');
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
case 'runtime_ready_and_cache': {
|
|
984
|
+
const archive = buildStandardArchive(dir);
|
|
985
|
+
archiveShaToNormalize = archive.sha256;
|
|
986
|
+
const manifest = standardManifest(archive);
|
|
987
|
+
const fetchImpl = async (input) => {
|
|
988
|
+
const url = input.toString();
|
|
989
|
+
if (url.includes('/manifest')) {
|
|
990
|
+
counters.manifestFetches += 1;
|
|
991
|
+
return Response.json({ ok: true, runtime: manifest });
|
|
992
|
+
}
|
|
993
|
+
counters.archiveFetches += 1;
|
|
994
|
+
return new Response(readFileSync(archive.path), { status: 200 });
|
|
995
|
+
};
|
|
996
|
+
const first = await resolveWith({ fetchImpl });
|
|
997
|
+
const second = await resolveWith({ fetchImpl });
|
|
998
|
+
record.result = runtimeResultProjection(first, cacheRoot);
|
|
999
|
+
record.second = runtimeResultProjection(second, cacheRoot);
|
|
1000
|
+
record.archiveFetches = counters.archiveFetches;
|
|
1001
|
+
record.installedManifestBytes = readFileSync(
|
|
1002
|
+
join(cacheRoot, 'darwin-arm64', archive.sha256, 'linzumi-editor-runtime.json'),
|
|
1003
|
+
'utf8'
|
|
1004
|
+
);
|
|
1005
|
+
record.tree = listTreeFiles(join(cacheRoot, 'darwin-arm64', archive.sha256));
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
case 'runtime_upstream_assets': {
|
|
1009
|
+
const packageRoot = 'code-server-4.103.2-linux-amd64';
|
|
1010
|
+
const archive = buildUpstreamArchive(dir, packageRoot);
|
|
1011
|
+
archiveShaToNormalize = archive.sha256;
|
|
1012
|
+
const octExtension = 'oct extension tarball bytes';
|
|
1013
|
+
const octServer = 'oct server tarball bytes';
|
|
1014
|
+
const documentStatePackage = JSON.stringify({ name: 'kandan.document-state-telemetry' });
|
|
1015
|
+
const documentStateExtension = 'module.exports = {};';
|
|
1016
|
+
const vsdaJs = 'export default {};';
|
|
1017
|
+
const vsdaWasm = 'wasm';
|
|
1018
|
+
const manifest = {
|
|
1019
|
+
version: '2026.05.02-code-server-4.103.2-linux-x64',
|
|
1020
|
+
platform: 'linux-x64',
|
|
1021
|
+
archiveUrl:
|
|
1022
|
+
'https://github.com/coder/code-server/releases/download/v4.103.2/code-server-4.103.2-linux-amd64.tar.gz',
|
|
1023
|
+
archiveSha256: archive.sha256,
|
|
1024
|
+
codeServerVersion: '4.103.2',
|
|
1025
|
+
codeServerBinPath: packageRoot + '/bin/code-server',
|
|
1026
|
+
manifestPath: 'linzumi-editor-runtime.json',
|
|
1027
|
+
assets: [
|
|
1028
|
+
{
|
|
1029
|
+
path: packageRoot + '/lib/vscode/node_modules/vsda/rust/web/vsda.js',
|
|
1030
|
+
sha256: sha256(vsdaJs),
|
|
1031
|
+
contentBase64: Buffer.from(vsdaJs).toString('base64'),
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
path: packageRoot + '/lib/vscode/node_modules/vsda/rust/web/vsda_bg.wasm',
|
|
1035
|
+
sha256: sha256(vsdaWasm),
|
|
1036
|
+
contentBase64: Buffer.from(vsdaWasm).toString('base64'),
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
path: 'kandan/editor_extensions/typefox.open-collaboration-tools.tar.gz',
|
|
1040
|
+
sha256: sha256(octExtension),
|
|
1041
|
+
url: '/api/v2/editor/runtime-assets/extensions/typefox.open-collaboration-tools.tar.gz',
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
path: 'kandan/editor_servers/open-collaboration-server.tar.gz',
|
|
1045
|
+
sha256: sha256(octServer),
|
|
1046
|
+
url: '/api/v2/editor/runtime-assets/servers/open-collaboration-server.tar.gz',
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
path: 'kandan/editor_extensions/kandan.document-state-telemetry/package.json',
|
|
1050
|
+
sha256: sha256(documentStatePackage),
|
|
1051
|
+
contentBase64: Buffer.from(documentStatePackage).toString('base64'),
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
path: 'kandan/editor_extensions/kandan.document-state-telemetry/out/extension.js',
|
|
1055
|
+
sha256: sha256(documentStateExtension),
|
|
1056
|
+
contentBase64: Buffer.from(documentStateExtension).toString('base64'),
|
|
1057
|
+
},
|
|
1058
|
+
],
|
|
1059
|
+
};
|
|
1060
|
+
const result = await resolveWith({
|
|
1061
|
+
platform: 'linux',
|
|
1062
|
+
arch: 'x64',
|
|
1063
|
+
fetchImpl: async (input, init) => {
|
|
1064
|
+
const url = input.toString();
|
|
1065
|
+
if (url.includes('/manifest')) {
|
|
1066
|
+
return Response.json({ ok: true, runtime: manifest });
|
|
1067
|
+
}
|
|
1068
|
+
if (url.includes('code-server-4.103.2-linux-amd64.tar.gz')) {
|
|
1069
|
+
authHeaderOnArchive = init?.headers !== undefined;
|
|
1070
|
+
return new Response(readFileSync(archive.path), { status: 200 });
|
|
1071
|
+
}
|
|
1072
|
+
if (url.includes('typefox.open-collaboration-tools.tar.gz')) {
|
|
1073
|
+
return new Response(octExtension, { status: 200 });
|
|
1074
|
+
}
|
|
1075
|
+
if (url.includes('open-collaboration-server.tar.gz')) {
|
|
1076
|
+
return new Response(octServer, { status: 200 });
|
|
1077
|
+
}
|
|
1078
|
+
return new Response('not found', { status: 404 });
|
|
1079
|
+
},
|
|
1080
|
+
});
|
|
1081
|
+
record.result = runtimeResultProjection(result, cacheRoot);
|
|
1082
|
+
record.authHeaderOnArchive = authHeaderOnArchive;
|
|
1083
|
+
record.installedVsdaJs =
|
|
1084
|
+
result.runtime === undefined
|
|
1085
|
+
? null
|
|
1086
|
+
: readFileSync(
|
|
1087
|
+
join(
|
|
1088
|
+
result.runtime.root,
|
|
1089
|
+
packageRoot,
|
|
1090
|
+
'lib',
|
|
1091
|
+
'vscode',
|
|
1092
|
+
'node_modules',
|
|
1093
|
+
'vsda',
|
|
1094
|
+
'rust',
|
|
1095
|
+
'web',
|
|
1096
|
+
'vsda.js'
|
|
1097
|
+
),
|
|
1098
|
+
'utf8'
|
|
1099
|
+
);
|
|
1100
|
+
record.installedCollabTarball =
|
|
1101
|
+
result.runtime === undefined
|
|
1102
|
+
? null
|
|
1103
|
+
: readFileSync(result.runtime.assets.collaborationExtensionTarball, 'utf8');
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
default:
|
|
1107
|
+
return { threw: true, message: 'unknown runtime scenario ' + String(testCase.scenario) };
|
|
1108
|
+
}
|
|
1109
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1110
|
+
return archiveShaToNormalize === null
|
|
1111
|
+
? record
|
|
1112
|
+
: substitute(record, archiveShaToNormalize, '$SHA');
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// --- Pure ops ---------------------------------------------------------------------------------
|
|
1116
|
+
|
|
1117
|
+
function launchOptionsFromScenario(scenario, dir) {
|
|
1118
|
+
switch (scenario) {
|
|
1119
|
+
case 'darwin_direct':
|
|
1120
|
+
return {
|
|
1121
|
+
codeServerBin: '/opt/homebrew/bin/code-server',
|
|
1122
|
+
port: 5173,
|
|
1123
|
+
cwd: '/Users/dev/code/project',
|
|
1124
|
+
userDataDir: '/private/var/folders/editor-user-data',
|
|
1125
|
+
extensionsDir: '/private/var/folders/editor-user-data/extensions',
|
|
1126
|
+
platform: 'darwin',
|
|
1127
|
+
sandboxExecBin: '/bin/sh',
|
|
1128
|
+
};
|
|
1129
|
+
case 'darwin_path': {
|
|
1130
|
+
const binDir = join(dir, 'bin');
|
|
1131
|
+
writeExecutable(join(binDir, 'code-server'), '#!/bin/sh\\nexit 0\\n');
|
|
1132
|
+
return {
|
|
1133
|
+
codeServerBin: 'code-server',
|
|
1134
|
+
port: 5173,
|
|
1135
|
+
cwd: '/Users/dev/code/project',
|
|
1136
|
+
userDataDir: '/private/var/folders/editor-user-data',
|
|
1137
|
+
extensionsDir: '/private/var/folders/editor-user-data/extensions',
|
|
1138
|
+
platform: 'darwin',
|
|
1139
|
+
sandboxExecBin: '/bin/sh',
|
|
1140
|
+
envPath: binDir,
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
case 'darwin_runtime_root': {
|
|
1144
|
+
const runtimeRoot = join(dir, 'runtime');
|
|
1145
|
+
const bin = join(runtimeRoot, 'bin', 'code-server');
|
|
1146
|
+
writeExecutable(bin, '#!/bin/sh\\nexit 0\\n');
|
|
1147
|
+
return {
|
|
1148
|
+
codeServerBin: bin,
|
|
1149
|
+
codeServerRuntimeRoot: runtimeRoot,
|
|
1150
|
+
port: 5173,
|
|
1151
|
+
cwd: '/Users/dev/code/project',
|
|
1152
|
+
userDataDir: '/private/var/folders/editor-user-data',
|
|
1153
|
+
extensionsDir: '/private/var/folders/editor-user-data/extensions',
|
|
1154
|
+
platform: 'darwin',
|
|
1155
|
+
sandboxExecBin: '/bin/sh',
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
case 'darwin_missing_path':
|
|
1159
|
+
return {
|
|
1160
|
+
codeServerBin: 'code-server',
|
|
1161
|
+
port: 5173,
|
|
1162
|
+
cwd: '/Users/dev/code/project',
|
|
1163
|
+
userDataDir: '/private/var/folders/editor-user-data',
|
|
1164
|
+
extensionsDir: '/private/var/folders/editor-user-data/extensions',
|
|
1165
|
+
platform: 'darwin',
|
|
1166
|
+
sandboxExecBin: '/bin/sh',
|
|
1167
|
+
envPath: '',
|
|
1168
|
+
};
|
|
1169
|
+
case 'darwin_no_extensions':
|
|
1170
|
+
return {
|
|
1171
|
+
codeServerBin: '/opt/homebrew/bin/code-server',
|
|
1172
|
+
port: 4171,
|
|
1173
|
+
cwd: '/workspace/project',
|
|
1174
|
+
userDataDir: '/tmp/editor-user-data',
|
|
1175
|
+
platform: 'darwin',
|
|
1176
|
+
envPath: '',
|
|
1177
|
+
};
|
|
1178
|
+
case 'linux_bwrap': {
|
|
1179
|
+
const runtimeRoot = join(dir, 'runtime');
|
|
1180
|
+
const bin = join(runtimeRoot, 'bin', 'code-server');
|
|
1181
|
+
const bwrapBin = join(dir, 'bin', 'bwrap');
|
|
1182
|
+
writeExecutable(bin, '#!/bin/sh\\nexit 0\\n');
|
|
1183
|
+
writeExecutable(bwrapBin, '#!/bin/sh\\nexit 0\\n');
|
|
1184
|
+
return {
|
|
1185
|
+
codeServerBin: bin,
|
|
1186
|
+
codeServerRuntimeRoot: runtimeRoot,
|
|
1187
|
+
port: 5173,
|
|
1188
|
+
cwd: '/workspace/project',
|
|
1189
|
+
userDataDir: '/tmp/editor-user-data',
|
|
1190
|
+
extensionsDir: '/tmp/editor-user-data/extensions',
|
|
1191
|
+
platform: 'linux',
|
|
1192
|
+
bubblewrapBin: bwrapBin,
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
case 'linux_missing_bwrap':
|
|
1196
|
+
return {
|
|
1197
|
+
codeServerBin: '/usr/local/bin/code-server',
|
|
1198
|
+
port: 5173,
|
|
1199
|
+
cwd: '/workspace/project',
|
|
1200
|
+
userDataDir: '/tmp/editor-user-data',
|
|
1201
|
+
extensionsDir: '/tmp/editor-user-data/extensions',
|
|
1202
|
+
platform: 'linux',
|
|
1203
|
+
envPath: '',
|
|
1204
|
+
};
|
|
1205
|
+
case 'unsupported':
|
|
1206
|
+
return {
|
|
1207
|
+
codeServerBin: '/usr/local/bin/code-server',
|
|
1208
|
+
port: 5173,
|
|
1209
|
+
cwd: '/workspace/project',
|
|
1210
|
+
userDataDir: '/tmp/editor-user-data',
|
|
1211
|
+
extensionsDir: '/tmp/editor-user-data/extensions',
|
|
1212
|
+
platform: 'freebsd',
|
|
1213
|
+
};
|
|
1214
|
+
default:
|
|
1215
|
+
throw new Error('unknown launch scenario ' + String(scenario));
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function collaborationFromJson(value) {
|
|
1220
|
+
if (value === null || value === undefined) {
|
|
1221
|
+
return undefined;
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
provider: value.provider,
|
|
1225
|
+
editorSessionId: value.editorSessionId,
|
|
1226
|
+
runtimeSessionId: value.runtimeSessionId,
|
|
1227
|
+
roomId: value.roomId,
|
|
1228
|
+
extensionAssetPath: value.extensionAssetPath,
|
|
1229
|
+
serverAssetPath: value.serverAssetPath,
|
|
1230
|
+
...(value.bootstrapToken === null || value.bootstrapToken === undefined
|
|
1231
|
+
? {}
|
|
1232
|
+
: { bootstrapToken: value.bootstrapToken }),
|
|
1233
|
+
...(value.serverPort === undefined || value.serverPort === null
|
|
1234
|
+
? {}
|
|
1235
|
+
: {
|
|
1236
|
+
serverPort: value.serverPort,
|
|
1237
|
+
serverUrl: value.serverUrl,
|
|
1238
|
+
bootstrapServerUrl: value.bootstrapServerUrl,
|
|
1239
|
+
collaborationStateUrl: value.collaborationStateUrl,
|
|
1240
|
+
}),
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function runningCollaborationFromJson(value) {
|
|
1245
|
+
if (value === null || value === undefined) {
|
|
1246
|
+
return undefined;
|
|
1247
|
+
}
|
|
1248
|
+
return {
|
|
1249
|
+
provider: value.provider,
|
|
1250
|
+
editorSessionId: value.editorSessionId,
|
|
1251
|
+
runtimeSessionId: value.runtimeSessionId,
|
|
1252
|
+
roomId: value.roomId,
|
|
1253
|
+
serverPort: value.serverPort,
|
|
1254
|
+
serverUrl: value.serverUrl,
|
|
1255
|
+
bootstrapServerUrl: value.bootstrapServerUrl,
|
|
1256
|
+
collaborationStateUrl: value.collaborationStateUrl,
|
|
1257
|
+
process: { kill: () => true },
|
|
1258
|
+
exited: Promise.resolve(),
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
async function handle(testCase) {
|
|
1263
|
+
switch (testCase.op) {
|
|
1264
|
+
case 'code_server_args':
|
|
1265
|
+
return outcome(() =>
|
|
1266
|
+
codeServerArgs(
|
|
1267
|
+
testCase.port,
|
|
1268
|
+
testCase.cwd,
|
|
1269
|
+
testCase.userDataDir,
|
|
1270
|
+
testCase.extensionsDir === null ? undefined : testCase.extensionsDir
|
|
1271
|
+
)
|
|
1272
|
+
);
|
|
1273
|
+
case 'code_server_env':
|
|
1274
|
+
return outcome(() =>
|
|
1275
|
+
codeServerEnv(
|
|
1276
|
+
testCase.env,
|
|
1277
|
+
testCase.cwd,
|
|
1278
|
+
testCase.userDataDir,
|
|
1279
|
+
runningCollaborationFromJson(testCase.collaboration)
|
|
1280
|
+
)
|
|
1281
|
+
);
|
|
1282
|
+
case 'settings_object':
|
|
1283
|
+
return outcome(() =>
|
|
1284
|
+
JSON.stringify(
|
|
1285
|
+
codeServerSettingsForTest(collaborationFromJson(testCase.collaboration)),
|
|
1286
|
+
null,
|
|
1287
|
+
2
|
|
1288
|
+
)
|
|
1289
|
+
);
|
|
1290
|
+
case 'collaboration_prepare':
|
|
1291
|
+
return outcome(() =>
|
|
1292
|
+
orNull(
|
|
1293
|
+
prepareLocalEditorCollaboration(
|
|
1294
|
+
collaborationFromJson(testCase.collaboration),
|
|
1295
|
+
testCase.runnerId,
|
|
1296
|
+
testCase.serverPort === null ? undefined : testCase.serverPort,
|
|
1297
|
+
testCase.browserBaseUrl
|
|
1298
|
+
)
|
|
1299
|
+
)
|
|
1300
|
+
);
|
|
1301
|
+
case 'browser_base_url':
|
|
1302
|
+
return outcome(() =>
|
|
1303
|
+
orNull(
|
|
1304
|
+
localEditorBrowserBaseUrlForTest(
|
|
1305
|
+
{
|
|
1306
|
+
type: 'start_local_editor',
|
|
1307
|
+
cwd: '/tmp',
|
|
1308
|
+
...(testCase.control === null ? {} : { browserBaseUrl: testCase.control }),
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
codeServerBin: undefined,
|
|
1312
|
+
allowedCwds: [],
|
|
1313
|
+
currentState: { status: 'disabled' },
|
|
1314
|
+
...(testCase.options === null ? {} : { browserBaseUrl: testCase.options }),
|
|
1315
|
+
}
|
|
1316
|
+
)
|
|
1317
|
+
)
|
|
1318
|
+
);
|
|
1319
|
+
case 'capabilities': {
|
|
1320
|
+
const runtime =
|
|
1321
|
+
testCase.hasRuntime === true
|
|
1322
|
+
? fakeInstalledEditorRuntime(scratch(), 'code-server')
|
|
1323
|
+
: undefined;
|
|
1324
|
+
const state =
|
|
1325
|
+
testCase.state.status === 'running'
|
|
1326
|
+
? {
|
|
1327
|
+
status: 'running',
|
|
1328
|
+
cwd: testCase.state.cwd,
|
|
1329
|
+
port: testCase.state.port,
|
|
1330
|
+
process: { kill: () => true },
|
|
1331
|
+
exited: Promise.resolve(),
|
|
1332
|
+
...(testCase.state.collaboration === null ||
|
|
1333
|
+
testCase.state.collaboration === undefined
|
|
1334
|
+
? {}
|
|
1335
|
+
: {
|
|
1336
|
+
collaboration: runningCollaborationFromJson(testCase.state.collaboration),
|
|
1337
|
+
}),
|
|
1338
|
+
}
|
|
1339
|
+
: { status: 'disabled' };
|
|
1340
|
+
return outcome(() => localEditorCapabilities(runtime, testCase.allowedCwds, state));
|
|
1341
|
+
}
|
|
1342
|
+
case 'node_runtime_executable':
|
|
1343
|
+
return outcome(() => nodeRuntimeExecutable(testCase.env, testCase.execPath));
|
|
1344
|
+
case 'wrapped_bin': {
|
|
1345
|
+
const dir = scratch();
|
|
1346
|
+
const binPath = join(dir, 'code-server');
|
|
1347
|
+
if (testCase.script !== null) {
|
|
1348
|
+
writeFileSync(binPath, testCase.script);
|
|
1349
|
+
}
|
|
1350
|
+
return outcome(() => wrappedCodeServerBinForTest(binPath));
|
|
1351
|
+
}
|
|
1352
|
+
case 'launch_prepare': {
|
|
1353
|
+
const dir = scratch();
|
|
1354
|
+
const options = launchOptionsFromScenario(testCase.scenario, dir);
|
|
1355
|
+
const result = prepareCodeServerLaunch(options);
|
|
1356
|
+
return substitute(result, dir, '$DIR');
|
|
1357
|
+
}
|
|
1358
|
+
case 'profile_prepare': {
|
|
1359
|
+
const dir = scratch();
|
|
1360
|
+
const scenarioRuntime = () => {
|
|
1361
|
+
switch (testCase.scenario) {
|
|
1362
|
+
case 'plain':
|
|
1363
|
+
case 'collab':
|
|
1364
|
+
return undefined;
|
|
1365
|
+
case 'with_runtime':
|
|
1366
|
+
return fakeInstalledEditorRuntime(dir, '/usr/local/bin/code-server');
|
|
1367
|
+
case 'missing_assets':
|
|
1368
|
+
return fakeInstalledEditorRuntime(dir, '/usr/local/bin/code-server', {
|
|
1369
|
+
withBrowserAssets: false,
|
|
1370
|
+
});
|
|
1371
|
+
case 'wrapper_runtime': {
|
|
1372
|
+
const realRoot = join(dir, 'real-code-server');
|
|
1373
|
+
const wrapperBin = join(dir, '.fake-runtime', 'bin', 'code-server');
|
|
1374
|
+
writeExecutable(
|
|
1375
|
+
wrapperBin,
|
|
1376
|
+
'#!/bin/sh\\nexec "' + join(realRoot, 'bin', 'code-server') + '" "$@"\\n'
|
|
1377
|
+
);
|
|
1378
|
+
writeBrowserAssetInputs(realRoot);
|
|
1379
|
+
const runtime = fakeInstalledEditorRuntime(dir, wrapperBin, {
|
|
1380
|
+
withBrowserAssets: false,
|
|
1381
|
+
});
|
|
1382
|
+
return runtime;
|
|
1383
|
+
}
|
|
1384
|
+
default:
|
|
1385
|
+
throw new Error('unknown profile scenario ' + String(testCase.scenario));
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
const runtime = scenarioRuntime();
|
|
1389
|
+
const collaboration =
|
|
1390
|
+
testCase.scenario === 'collab'
|
|
1391
|
+
? prepareLocalEditorCollaboration(
|
|
1392
|
+
collaborationFromJson(testCase.collaboration),
|
|
1393
|
+
'runner-a',
|
|
1394
|
+
5555,
|
|
1395
|
+
'https://linzumi.io:4140'
|
|
1396
|
+
)
|
|
1397
|
+
: undefined;
|
|
1398
|
+
const result = prepareCodeServerProfile(collaboration, runtime);
|
|
1399
|
+
const record = { ok: result.ok };
|
|
1400
|
+
if (result.ok) {
|
|
1401
|
+
record.settingsBytes = readFileSync(
|
|
1402
|
+
join(result.userDataDir, 'User', 'settings.json'),
|
|
1403
|
+
'utf8'
|
|
1404
|
+
);
|
|
1405
|
+
record.tmpExists = existsSync(join(result.userDataDir, 'tmp'));
|
|
1406
|
+
record.extensionsDirName = relative(result.userDataDir, result.extensionsDir);
|
|
1407
|
+
record.collaborationServerDirName = relative(
|
|
1408
|
+
result.userDataDir,
|
|
1409
|
+
result.collaborationServerDir
|
|
1410
|
+
);
|
|
1411
|
+
record.documentStateInstalled = existsSync(
|
|
1412
|
+
join(result.extensionsDir, 'kandan.document-state-telemetry', 'package.json')
|
|
1413
|
+
);
|
|
1414
|
+
if (runtime !== undefined && testCase.scenario === 'with_runtime') {
|
|
1415
|
+
record.repairedGitBase = readFileSync(
|
|
1416
|
+
join(
|
|
1417
|
+
runtime.root,
|
|
1418
|
+
'lib',
|
|
1419
|
+
'vscode',
|
|
1420
|
+
'extensions',
|
|
1421
|
+
'git-base',
|
|
1422
|
+
'dist',
|
|
1423
|
+
'browser',
|
|
1424
|
+
'extension.js'
|
|
1425
|
+
),
|
|
1426
|
+
'utf8'
|
|
1427
|
+
);
|
|
1428
|
+
record.repairedMergeConflict = readFileSync(
|
|
1429
|
+
join(
|
|
1430
|
+
runtime.root,
|
|
1431
|
+
'lib',
|
|
1432
|
+
'vscode',
|
|
1433
|
+
'extensions',
|
|
1434
|
+
'merge-conflict',
|
|
1435
|
+
'dist',
|
|
1436
|
+
'browser',
|
|
1437
|
+
'mergeConflictMain.js'
|
|
1438
|
+
),
|
|
1439
|
+
'utf8'
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
if (testCase.scenario === 'wrapper_runtime') {
|
|
1443
|
+
record.repairedGitBase = readFileSync(
|
|
1444
|
+
join(
|
|
1445
|
+
dir,
|
|
1446
|
+
'real-code-server',
|
|
1447
|
+
'lib',
|
|
1448
|
+
'vscode',
|
|
1449
|
+
'extensions',
|
|
1450
|
+
'git-base',
|
|
1451
|
+
'dist',
|
|
1452
|
+
'browser',
|
|
1453
|
+
'extension.js'
|
|
1454
|
+
),
|
|
1455
|
+
'utf8'
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
rmSync(result.userDataDir, { recursive: true, force: true });
|
|
1459
|
+
} else {
|
|
1460
|
+
record.reason = result.reason;
|
|
1461
|
+
}
|
|
1462
|
+
return record;
|
|
1463
|
+
}
|
|
1464
|
+
case 'runtime_platform':
|
|
1465
|
+
return outcome(() => orNull(editorRuntimePlatform(testCase.platform, testCase.arch)));
|
|
1466
|
+
case 'status_payload':
|
|
1467
|
+
return outcome(() => editorRuntimeStatusPayload(testCase.status));
|
|
1468
|
+
case 'manifest_normalize':
|
|
1469
|
+
return outcome(() => normalizeManifestForTest(testCase.value));
|
|
1470
|
+
case 'runtime_install_root':
|
|
1471
|
+
return outcome(() =>
|
|
1472
|
+
runtimeInstallRootForTest(testCase.cacheRoot, {
|
|
1473
|
+
platform: testCase.platform,
|
|
1474
|
+
archiveSha256: testCase.archiveSha256,
|
|
1475
|
+
})
|
|
1476
|
+
);
|
|
1477
|
+
case 'code_server_runtime_root':
|
|
1478
|
+
return outcome(() => codeServerRuntimeRootForTest(testCase.binPath));
|
|
1479
|
+
case 'same_origin':
|
|
1480
|
+
return outcome(() => sameOriginForTest(new URL(testCase.left), new URL(testCase.right)));
|
|
1481
|
+
case 'default_cache_root':
|
|
1482
|
+
return outcome(() => substitute(defaultEditorRuntimeCacheRootForTest(), homedir(), '$HOME'));
|
|
1483
|
+
case 'start_editor_flow':
|
|
1484
|
+
return handleStartEditorFlow(testCase);
|
|
1485
|
+
case 'resolve_runtime_flow':
|
|
1486
|
+
return handleResolveRuntimeFlow(testCase);
|
|
1487
|
+
default:
|
|
1488
|
+
return { threw: true, message: 'unknown op ' + String(testCase.op) };
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
let stdin = '';
|
|
1493
|
+
process.stdin.setEncoding('utf8');
|
|
1494
|
+
for await (const chunk of process.stdin) {
|
|
1495
|
+
stdin += chunk;
|
|
1496
|
+
}
|
|
1497
|
+
const cases = JSON.parse(stdin);
|
|
1498
|
+
const results = [];
|
|
1499
|
+
for (const testCase of cases) {
|
|
1500
|
+
results.push(await handle(testCase));
|
|
1501
|
+
}
|
|
1502
|
+
process.stdout.write(JSON.stringify(results));
|
|
1503
|
+
`;
|
|
1504
|
+
|
|
1505
|
+
await mkdir(join(packageRoot, '.differential'), { recursive: true });
|
|
1506
|
+
|
|
1507
|
+
await build({
|
|
1508
|
+
stdin: {
|
|
1509
|
+
contents: driverSource,
|
|
1510
|
+
resolveDir: packageRoot,
|
|
1511
|
+
sourcefile: 'editor-parity-driver.ts',
|
|
1512
|
+
loader: 'ts',
|
|
1513
|
+
},
|
|
1514
|
+
bundle: true,
|
|
1515
|
+
platform: 'node',
|
|
1516
|
+
target: 'node20',
|
|
1517
|
+
format: 'esm',
|
|
1518
|
+
outfile: join(packageRoot, '.differential/editor-parity-oracle.mjs'),
|
|
1519
|
+
minify: false,
|
|
1520
|
+
sourcemap: false,
|
|
1521
|
+
legalComments: 'none',
|
|
1522
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
1523
|
+
});
|