UNPKG

@typescript/server-replay

Version:
269 lines 10.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.canonicalizePath = canonicalizePath; exports.runReplay = runReplay; const sh = __importStar(require("@typescript/server-harness")); const fs = __importStar(require("fs")); const process = __importStar(require("process")); const path = __importStar(require("path")); const readline = __importStar(require("readline")); const events = __importStar(require("events")); function canonicalizePath(p) { // Resolve relative paths now, before we chdir // Match the slashes used in server requests return path.resolve(p).replace(/\\/g, "/"); } async function runReplay(options) { const testDir = canonicalizePath(options.project); const requestsPath = canonicalizePath(options.requests); const serverPath = canonicalizePath(options.server); const traceDir = options.traceDir && canonicalizePath(options.traceDir); const logDir = options.logDir && canonicalizePath(options.logDir); const inspectPort = options.inspectPort && +options.inspectPort; const unattended = !!options.unattended; const simple = !!options.simple; const superSimple = !!options.superSimple; // Needed for excludedDirectories process.chdir(testDir); const rl = readline.createInterface({ input: fs.createReadStream(requestsPath), crlfDelay: Infinity, }); let rootDirPlaceholder = "@PROJECT_ROOT@"; let serverArgs = [ "--disableAutomaticTypingAcquisition", ]; let firstLine = true; let sawExit = false; let requests = []; rl.on('line', line => { try { // Ignore blank lines if (line.trim().length === 0) { return; } if (firstLine) { const obj = JSON.parse(line); if (!obj.command) { rootDirPlaceholder = obj.rootDirPlaceholder ?? rootDirPlaceholder; serverArgs = obj.serverArgs ?? serverArgs; return; } } const request = JSON.parse(line.replace(new RegExp(rootDirPlaceholder, "g"), testDir)); if (request.command === "updateOpen") { for (const openFile of request.arguments.openFiles) { const openFileContents = fs.readFileSync(openFile.file, { encoding: "utf-8" }); openFile.fileContent = openFileContents; } } else if (request.command === "applyChangedToOpenFiles") { for (const openFile of request.arguments.openFiles) { const openFileContents = fs.readFileSync(openFile.fileName, { encoding: "utf-8" }); openFile.content = openFileContents; } } // Drop exit requests in unattended mode - we need to tear down more explicitly const isExit = request.command === "exit"; sawExit = sawExit || isExit; if (!isExit || !unattended) { requests.push(request); } } catch { console.log(`Bad input "${line}"`); if (unattended) { process.exit(2); } } finally { firstLine = false; } }); await events.EventEmitter.once(rl, 'close'); if (!requests.length) { if (!unattended) { console.log("No requests to replay"); } process.exit(0); } if (!sawExit && !unattended) { requests.push({ "seq": 999999, "command": "exit" }); } if (simple) { const newRequests = []; let i = 0; if (requests[i].command === "configure") { newRequests.push(requests[i]); i++; } let j = requests.length - 1; if (requests[j].command === "exit") { j--; } for (; i < j; i++) { const req = requests[i]; if (req.command === "updateOpen" || req.command === "applyChangedToOpenFiles") { if (req.arguments.openFiles?.length || req.arguments.closedFiles?.length) { newRequests.push(req); } } } for (j = Math.max(i, j); j < requests.length; j++) { newRequests.push(requests[j]); } requests = newRequests; } else if (superSimple) { const newRequests = []; let h = 0; if (requests[h].command === "configure") { newRequests.push(requests[h]); h++; } let i = requests.length - 1; for (; i >= h; i--) { const req = requests[i]; if (req.command === "updateOpen" || req.command === "applyChangedToOpenFiles") { if (req.arguments.openFiles?.length) { // We're not opening other files, so changeFiles and closeFiles can only cause problems, if they're present req.arguments.changedFiles = []; req.arguments.closedFiles = []; newRequests.push(req); break; } } } // NB: i === h-1 if no file open request was found and i >= h-1 in all cases let j = requests.length - 1; if (requests[j].command === "exit") { if (j - 1 > i) { newRequests.push(requests[j - 1]); } newRequests.push(requests[j]); } else if (j > i) { newRequests.push(requests[j]); } requests = newRequests; } if (traceDir) { await fs.promises.mkdir(traceDir, { recursive: true }); serverArgs.push("--traceDirectory", traceDir); } if (logDir) { await fs.promises.mkdir(logDir, { recursive: true }); serverArgs.push("--logVerbosity", "verbose"); serverArgs.push("--logFile", path.join(logDir, "tsserver.PID.log")); } const nodeArgs = [ "--max-old-space-size=4096", "--expose-gc", "--stack-size=2048", // "--require=E:/tsserver-stress/node_modules/pprof-it/dist/index.js", ]; if (inspectPort) { nodeArgs.push(`--inspect-brk=${inspectPort}`); } let exitRequested = false; const server = sh.launchServer(serverPath, serverArgs, nodeArgs); // On Linux, it's idiomatic to shut down your child processes when you receive SIGTERM. // This helps us ensure clean teardown during lab runs. // NB: As of Node 16, SIGTERM never fires on Windows. process.once("SIGTERM", async () => { exitRequested = true; // Shouldn't matter, but might as well await server.kill(); // This is a sneaky way to invoke node's default SIGTERM handler process.kill(process.pid, "SIGTERM"); }); server.on("close", (code, signal) => { if (!unattended || !exitRequested || code || signal) { console.log(`${exitRequested ? "Shut down" : "Exited unexpectedly"}${code ? ` with code ${code}` : signal ? ` with signal ${signal}` : ""}`); } if (unattended && !exitRequested) { process.exit(3); } }); server.on("communicationError", async (err) => { console.error(`Error communicating with server:\n${err}`); if (unattended) { exitRequested = true; // Suppress "exit" event handler await server.kill(); process.exit(7); } }); server.on("event", async (e) => { if (e.event === "projectLanguageServiceState" && !e.body.languageServiceEnabled) { console.log(`Language service disabled for ${e.body.projectName ? path.normalize(e.body.projectName) : "unknown project"}`); if (unattended) { exitRequested = true; // Suppress "exit" event handler await server.kill(); process.exit(4); } } }); for (const request of requests) { exitRequested = exitRequested || request.command === "exit"; if (!unattended) console.log(`${request.seq}\t${request.command}`); const response = await server.message(request); if (response && !response.success && response.message !== "No content available.") { if (unattended) { console.log(JSON.stringify(response)); // Print on a single line - includes request seq and error message, if any try { await exitOrKillServer(); } catch { // Ignore errors during shutdown } process.exit(5); } console.log(request); console.log(response); } } if (unattended) { if (!await exitOrKillServer()) { // Server didn't exit cleanly and had to be killed process.exit(6); } } async function exitOrKillServer() { exitRequested = true; // Suppress "exit" event handler return await server.exitOrKill(5000); } } //# sourceMappingURL=stradaReplay.js.map