This commit is contained in:
2026-09-10 19:18:15 +02:00
parent 3a3d70efeb
commit 5993da4ce6
21 changed files with 2916 additions and 340 deletions
+89
View File
@@ -0,0 +1,89 @@
// Probe: which Node APIs does the VS Code Electron binary provide in
// ELECTRON_RUN_AS_NODE mode? Run with:
// ELECTRON_RUN_AS_NODE=1 "<Code.exe>" probe.cjs
const out = {};
try {
out.nodeVersion = process.versions.node;
out.electronVersion = process.versions.electron ?? null;
out.isElectronRunAsNode = process.env.ELECTRON_RUN_AS_NODE === "1";
out.execPath = process.execPath;
out.platform = process.platform;
} catch (e) {
out.baseError = String(e);
}
const modules = [
"node:fs",
"node:fs/promises",
"node:path",
"node:os",
"node:http",
"node:readline",
"node:zlib",
"node:crypto",
"node:util",
"node:net",
"node:child_process",
"node:test",
"node:assert",
"node:url",
"node:events",
];
out.modules = {};
for (const m of modules) {
try {
require(m);
out.modules[m] = "ok";
} catch (e) {
out.modules[m] = "FAIL: " + (e && e.code ? e.code : String(e));
}
}
// The APIs the MCP server actually depends on.
try {
const { parseLoadedXml } = require("../out/agent/forwardRefs.js");
const parsed = parseLoadedXml('<AssetDeclaration xmlns="uri:ea.com:eala:asset"><GameObject id="X"/></AssetDeclaration>');
out.forwardRefs = "ok: elements=" + parsed.parse.elements.length;
} catch (e) {
out.forwardRefs = "FAIL: " + String(e).slice(0, 160);
}
try {
const { startLocalServer } = require("../out/agent/localServer.js");
out.localServer = typeof startLocalServer === "function" ? "loadable" : "missing";
} catch (e) {
out.localServer = "FAIL: " + String(e).slice(0, 160);
}
// Async smoke test: gzip round-trip + http listen (both used at runtime).
(async () => {
try {
const { gzip, gunzip } = require("node:zlib");
const { promisify } = require("node:util");
const buf = await promisify(gzip)(Buffer.from("hello"));
const back = await promisify(gunzip)(buf);
out.zlibRoundTrip = back.toString() === "hello" ? "ok" : "mismatch";
} catch (e) {
out.zlibRoundTrip = "FAIL: " + String(e).slice(0, 120);
}
try {
const { startLocalServer } = require("../out/agent/localServer.js");
const handle = await startLocalServer({
getIndex: () => null,
listProjects: () => [],
token: "probe",
});
const res = await fetch(`http://127.0.0.1:${handle.port}/status`, {
headers: { authorization: "Bearer probe" },
});
const body = await res.json();
out.httpServer = "ok: " + JSON.stringify(body);
await handle.close();
} catch (e) {
out.httpServer = "FAIL: " + String(e).slice(0, 160);
}
console.log(JSON.stringify(out, null, 2));
})();
+89
View File
@@ -0,0 +1,89 @@
/**
* Starts a real local live-index server with a fake in-memory index and
* registers it exactly like the extension does (instance file + per-project
* endpoint + merged manifest). Used by the integration smoke test.
*
* Prints the agent home as its first stdout line, then stays alive.
*/
const { startLocalServer } = require("../out/agent/localServer.js");
const { writeInstance, writeManifest } = require("../out/agent/instances.js");
const { writeEndpoint, writeEndpointForProject } = require("../out/agent/endpoint.js");
const { mkdirSync, writeFileSync } = require("node:fs");
const { join } = require("node:path");
const PROJECT = process.env.RA3_PROJECT || "D:/Mods/Alpha";
const OTHER = "D:/Mods/Beta";
// The agent home is passed in, so the caller knows it without reading stdout.
const home = process.argv[2];
if (!home) {
console.error("usage: serve-fake-index.cjs <agentHome>");
process.exit(2);
}
function def(type, id, file, line) {
return { type, id, file, line, origin: "project", stream: "static" };
}
const unitFile = `${PROJECT}/Data/AthenaCannon.xml`;
const unit = def("GameObject", "AthenaCannon", unitFile, 2);
const weapon = def("WeaponTemplate", "AthenaCannonWeapon", `${PROJECT}/Data/Weapon.xml`, 88);
const index = {
projectDir: PROJECT,
sdkDir: "",
complete: true,
phase: "art",
stale: false,
assets: new Map([
["GameObject", new Map([["athenacannon", [unit]]])],
["WeaponTemplate", new Map([["athenacannonweapon", [weapon]]])],
]),
assetsById: new Map([
["athenacannon", [unit]],
["athenacannonweapon", [weapon]],
]),
defines: new Map([["d", [{ name: "D", value: "1", file: unitFile, line: 1, origin: "project" }]]]),
files: new Map(),
streams: [{ name: "static", entry: `${PROJECT}/Data/Mod.xml`, files: new Set([unitFile.toLowerCase().replace(/\\/g, "/")]) }],
manifests: new Map(),
sourceCandidates: [],
diagnostics: [],
references: new Map([
[`GameObject\u0000athenacannon\u0000${unitFile}\u00002`, [{ file: `${PROJECT}/Data/Other.xml`, line: 3, start: 1, end: 2, kind: "attr" }]],
]),
recordsHashes: new Map(),
stats: {
projectDir: PROJECT, sdkDir: "", phase: "art", complete: true,
indexedFiles: 2, parsedFiles: 2, shallowScannedFiles: 0, deferredArtFiles: 0,
shallowCacheHits: 0, recordsCacheHits: 0, resolveCacheHits: 0, resolveCalls: 0,
snapshotHits: 0, snapshotFallbacks: 0, candidatesMs: 0, walkMs: 0, artScanMs: 0,
assetCount: 2, referenceCount: 1, defineCount: 1, manifestFiles: 0,
manifestAssetCount: 0, streams: 1, sourceCandidates: 0, elapsedMs: 1,
},
};
(async () => {
mkdirSync(home, { recursive: true });
const handle = await startLocalServer({
getIndex: (projectDir) => (!projectDir || projectDir === PROJECT ? index : null),
listProjects: () => [PROJECT, OTHER],
loadFile: async () => null,
});
const endpoint = {
instanceId: "smoke-1",
url: `http://127.0.0.1:${handle.port}`,
token: handle.token,
projectDir: PROJECT,
projects: [PROJECT, OTHER],
processId: process.pid,
updatedAt: new Date().toISOString(),
};
await writeEndpointForProject(PROJECT, { ...endpoint, projectDir: PROJECT }, home);
await writeEndpoint(endpoint, home);
await writeInstance(endpoint, home);
await writeManifest([endpoint], home);
// Signal readiness through a file so the caller never has to parse stdout.
writeFileSync(join(home, "READY"), `${handle.port}\n`);
// Stay alive until killed.
setInterval(() => {}, 1 << 30);
})();