@linghun/pre-engine-win32-x64 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,427 @@
1
+ "use strict";
2
+ const readline = require("readline");
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+ const { spawn, spawnSync } = require("child_process");
6
+
7
+ // --- LSP JSON-RPC framing ---
8
+ let msgId = 0;
9
+ function lspEncode(obj) {
10
+ const body = JSON.stringify(obj);
11
+ return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
12
+ }
13
+ function lspRequest(method, params) {
14
+ return lspEncode({ jsonrpc: "2.0", id: ++msgId, method, params });
15
+ }
16
+ function lspNotify(method, params) {
17
+ return lspEncode({ jsonrpc: "2.0", method, params });
18
+ }
19
+
20
+ function fileUri(p) {
21
+ const abs = path.resolve(p).replace(/\\/g, "/");
22
+ return abs.startsWith("/") ? `file://${abs}` : `file:///${abs}`;
23
+ }
24
+
25
+ function normUri(uri) { return uri.toLowerCase(); }
26
+
27
+ // --- gopls session state ---
28
+ let goplsProc = null;
29
+ let goplsRoot = null;
30
+ let goplsReady = false;
31
+ let goplsBuffer = "";
32
+ let goplsExpectedLen = -1;
33
+ let goplsInitResolve = null;
34
+ let goplsInitReject = null;
35
+ let goplsDiagnostics = new Map();
36
+ let goplsDiagTimers = new Map();
37
+ let goplsDiagWaiters = [];
38
+ let openDocs = new Map();
39
+ let goplsDocOpenTime = new Map();
40
+ let goplsEmptyCount = new Map();
41
+ let goplsWarmedUp = false;
42
+ let goplsStartingPromise = null;
43
+
44
+ const BOOTSTRAP_BUDGET_MS = 3000;
45
+ const WARM_SETTLE_MS = 1500;
46
+
47
+ function findGopls() {
48
+ const whichCmd = process.platform === "win32" ? "where.exe" : "which";
49
+ const r = spawnSync(whichCmd, ["gopls"], {
50
+ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
51
+ });
52
+ if (r.status === 0) {
53
+ const line = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean)[0];
54
+ return line || null;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ function killGopls() {
60
+ if (goplsProc) { try { goplsProc.kill(); } catch {} goplsProc = null; }
61
+ goplsReady = false;
62
+ goplsWarmedUp = false;
63
+ goplsStartingPromise = null;
64
+ goplsInitResolve = null;
65
+ goplsInitReject = null;
66
+ goplsDiagWaiters.forEach(w => w.resolve([]));
67
+ goplsDiagWaiters = [];
68
+ goplsDiagTimers.forEach(t => clearTimeout(t));
69
+ goplsDiagTimers.clear();
70
+ openDocs.clear();
71
+ goplsDocOpenTime.clear();
72
+ goplsEmptyCount.clear();
73
+ }
74
+
75
+ function onGoplsData(chunk) {
76
+ goplsBuffer += chunk.toString();
77
+ while (true) {
78
+ if (goplsExpectedLen === -1) {
79
+ const headerEnd = goplsBuffer.indexOf("\r\n\r\n");
80
+ if (headerEnd === -1) break;
81
+ const header = goplsBuffer.slice(0, headerEnd);
82
+ const m = header.match(/Content-Length:\s*(\d+)/i);
83
+ if (!m) { goplsBuffer = goplsBuffer.slice(headerEnd + 4); continue; }
84
+ goplsExpectedLen = parseInt(m[1], 10);
85
+ goplsBuffer = goplsBuffer.slice(headerEnd + 4);
86
+ }
87
+ if (goplsBuffer.length < goplsExpectedLen) break;
88
+ const body = goplsBuffer.slice(0, goplsExpectedLen);
89
+ goplsBuffer = goplsBuffer.slice(goplsExpectedLen);
90
+ goplsExpectedLen = -1;
91
+ handleGoplsMessage(body);
92
+ }
93
+ }
94
+
95
+ function handleGoplsMessage(body) {
96
+ let msg;
97
+ try { msg = JSON.parse(body); } catch { return; }
98
+
99
+ if (msg.method && msg.id != null) {
100
+ goplsProc.stdin.write(lspEncode({ jsonrpc: "2.0", id: msg.id, result: null }));
101
+ return;
102
+ }
103
+
104
+ if (msg.id && msg.result !== undefined && goplsInitResolve && !goplsReady) {
105
+ goplsProc.stdin.write(lspNotify("initialized", {}));
106
+ goplsReady = true;
107
+ const resolve = goplsInitResolve;
108
+ goplsInitResolve = null;
109
+ goplsInitReject = null;
110
+ resolve();
111
+ return;
112
+ }
113
+
114
+ if (msg.method === "textDocument/publishDiagnostics" && msg.params) {
115
+ const { uri, diagnostics } = msg.params;
116
+ const uriKey = normUri(uri);
117
+ const issues = (diagnostics || [])
118
+ .filter(d => d.severity === 1)
119
+ .map(d => ({
120
+ file: uriToRel(uri),
121
+ line: (d.range && d.range.start) ? d.range.start.line + 1 : 1,
122
+ kind: "type_error",
123
+ detail: d.message || "unknown error",
124
+ source: "go-deep-layer",
125
+ }));
126
+ goplsDiagnostics.set(uriKey, issues);
127
+ if (goplsDiagTimers.has(uriKey)) clearTimeout(goplsDiagTimers.get(uriKey));
128
+ if (issues.length > 0) {
129
+ goplsWarmedUp = true;
130
+ goplsEmptyCount.set(uriKey, 0);
131
+ goplsDiagTimers.delete(uriKey);
132
+ goplsDiagWaiters = goplsDiagWaiters.filter(w => {
133
+ if (w.uri === uriKey) { w.resolve(issues); return false; }
134
+ return true;
135
+ });
136
+ } else if (goplsWarmedUp) {
137
+ const count = (goplsEmptyCount.get(uriKey) || 0) + 1;
138
+ goplsEmptyCount.set(uriKey, count);
139
+ if (count >= 2) {
140
+ goplsDiagTimers.delete(uriKey);
141
+ goplsDiagWaiters = goplsDiagWaiters.filter(w => {
142
+ if (w.uri === uriKey) { w.resolve([]); return false; }
143
+ return true;
144
+ });
145
+ } else {
146
+ const openedAt = goplsDocOpenTime.get(uriKey) || 0;
147
+ const elapsed = Date.now() - openedAt;
148
+ const remaining = Math.max(0, WARM_SETTLE_MS - elapsed);
149
+ goplsDiagTimers.set(uriKey, setTimeout(() => {
150
+ goplsDiagTimers.delete(uriKey);
151
+ const final = goplsDiagnostics.get(uriKey) || [];
152
+ goplsDiagWaiters = goplsDiagWaiters.filter(w => {
153
+ if (w.uri === uriKey) { w.resolve(final); return false; }
154
+ return true;
155
+ });
156
+ }, remaining));
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ function uriToRel(uri) {
163
+ const decoded = decodeURIComponent(uri.replace(/^file:\/\/\/?/, "").replace(/^([a-z]):/, (_, d) => d.toUpperCase() + ":"));
164
+ if (goplsRoot) {
165
+ let rel = path.relative(goplsRoot, decoded).replace(/\\/g, "/");
166
+ if (!rel.startsWith("..")) return rel;
167
+ }
168
+ return decoded.replace(/\\/g, "/");
169
+ }
170
+
171
+ function waitForDiag(uri, timeoutMs) {
172
+ const uriKey = normUri(uri);
173
+ return new Promise(resolve => {
174
+ if (goplsDiagnostics.has(uriKey) && !goplsDiagTimers.has(uriKey)) {
175
+ resolve(goplsDiagnostics.get(uriKey));
176
+ return;
177
+ }
178
+ const timer = setTimeout(() => {
179
+ goplsDiagWaiters = goplsDiagWaiters.filter(w => w.resolve !== resolve);
180
+ resolve(goplsDiagnostics.get(uriKey) || []);
181
+ }, timeoutMs);
182
+ goplsDiagWaiters.push({ uri: uriKey, resolve: issues => { clearTimeout(timer); resolve(issues); } });
183
+ });
184
+ }
185
+
186
+ function startGopls(root) {
187
+ return new Promise((resolve, reject) => {
188
+ const goplsPath = findGopls();
189
+ if (!goplsPath) { reject(new Error("gopls not found")); return; }
190
+
191
+ goplsRoot = root;
192
+ goplsDiagnostics.clear();
193
+ openDocs.clear();
194
+ goplsWarmedUp = false;
195
+ goplsBuffer = "";
196
+ goplsExpectedLen = -1;
197
+ goplsReady = false;
198
+
199
+ goplsProc = spawn(goplsPath, ["serve"], {
200
+ cwd: root,
201
+ stdio: ["pipe", "pipe", "pipe"],
202
+ windowsHide: true,
203
+ });
204
+ goplsProc.on("error", e => { goplsProc = null; reject(e); });
205
+ goplsProc.on("exit", () => { goplsProc = null; goplsReady = false; });
206
+ goplsProc.stdout.on("data", chunk => onGoplsData(chunk));
207
+
208
+ goplsInitResolve = resolve;
209
+ goplsInitReject = reject;
210
+
211
+ goplsProc.stdin.write(lspRequest("initialize", {
212
+ processId: process.pid,
213
+ capabilities: { textDocument: { publishDiagnostics: { relatedInformation: false } } },
214
+ rootUri: fileUri(root),
215
+ workspaceFolders: [{ uri: fileUri(root), name: path.basename(root) }],
216
+ }));
217
+
218
+ setTimeout(() => {
219
+ if (!goplsReady) { killGopls(); reject(new Error("gopls init timeout (15s)")); }
220
+ }, 15000);
221
+ });
222
+ }
223
+
224
+ function eagerWarmUp(root) {
225
+ if (goplsStartingPromise) return goplsStartingPromise;
226
+ goplsStartingPromise = startGopls(root).then(() => {
227
+ const candidates = ["main.go", "type_error.go"];
228
+ for (const rel of candidates) {
229
+ const abs = path.join(root, rel);
230
+ if (fs.existsSync(abs)) {
231
+ sendDocOpen(abs, fileUri(abs));
232
+ break;
233
+ }
234
+ }
235
+ }).catch(() => {}).finally(() => { goplsStartingPromise = null; });
236
+ return goplsStartingPromise;
237
+ }
238
+
239
+ function sendDocOpen(absPath, uri) {
240
+ let text;
241
+ try { text = fs.readFileSync(absPath, "utf8"); } catch { return false; }
242
+ const version = (openDocs.get(uri) || 0) + 1;
243
+ openDocs.set(uri, version);
244
+ const uriKey = normUri(uri);
245
+ goplsDiagnostics.delete(uriKey);
246
+ goplsEmptyCount.set(uriKey, 0);
247
+ goplsDocOpenTime.set(uriKey, Date.now());
248
+
249
+ if (version === 1) {
250
+ goplsProc.stdin.write(lspNotify("textDocument/didOpen", {
251
+ textDocument: { uri, languageId: "go", version, text },
252
+ }));
253
+ } else {
254
+ goplsProc.stdin.write(lspNotify("textDocument/didChange", {
255
+ textDocument: { uri, version },
256
+ contentChanges: [{ text }],
257
+ }));
258
+ }
259
+ return true;
260
+ }
261
+
262
+ async function queryLsp(root, files) {
263
+ if (!goplsProc || !goplsReady || goplsRoot !== root) {
264
+ await startGopls(root);
265
+ }
266
+
267
+ const DIAG_TIMEOUT = 25000;
268
+ const uris = files.map(f => {
269
+ const abs = path.isAbsolute(f) ? f : path.join(root, f);
270
+ return { abs, uri: fileUri(abs) };
271
+ });
272
+
273
+ for (const { abs, uri } of uris) sendDocOpen(abs, uri);
274
+
275
+ const results = await Promise.all(uris.map(({ uri }) => waitForDiag(uri, DIAG_TIMEOUT)));
276
+ return results.flat();
277
+ }
278
+
279
+ // --- go build fallback ---
280
+ let cachedGoPath = null;
281
+ function findGo() {
282
+ if (cachedGoPath) return cachedGoPath;
283
+ const whichCmd = process.platform === "win32" ? "where.exe" : "which";
284
+ const r = spawnSync(whichCmd, ["go"], {
285
+ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
286
+ });
287
+ if (r.status === 0) {
288
+ const lines = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
289
+ const preferred = (process.platform === "win32" ? lines.find(l => l.endsWith(".exe")) : null) || lines[0];
290
+ if (preferred) { cachedGoPath = preferred; return preferred; }
291
+ }
292
+ return null;
293
+ }
294
+
295
+ function findGoMod(filePath, fallbackRoot) {
296
+ let dir = path.dirname(path.isAbsolute(filePath) ? filePath : path.resolve(fallbackRoot, filePath));
297
+ const root = path.parse(dir).root;
298
+ while (true) {
299
+ const candidate = path.join(dir, "go.mod");
300
+ if (fs.existsSync(candidate)) return dir;
301
+ const parent = path.dirname(dir);
302
+ if (parent === dir || dir === root) break;
303
+ dir = parent;
304
+ }
305
+ return null;
306
+ }
307
+
308
+ function runGoBuild(root, files) {
309
+ const goPath = findGo();
310
+ if (!goPath) return { error: "go not found" };
311
+
312
+ const modDir = files.length > 0 ? findGoMod(files[0], root) : null;
313
+ const cwd = modDir || root;
314
+
315
+ let result;
316
+ try {
317
+ result = spawnSync(goPath, ["build", "./..."], {
318
+ cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
319
+ windowsHide: true, timeout: 30000,
320
+ });
321
+ } catch (e) { return { error: `go exec: ${e.message}` }; }
322
+ if (result.error) {
323
+ return { error: result.error.code === "ETIMEDOUT" ? "go build timeout" : result.error.message };
324
+ }
325
+
326
+ const targetFiles = files.map(f =>
327
+ path.relative(cwd, path.isAbsolute(f) ? f : path.resolve(root, f)).replace(/\\/g, "/")
328
+ );
329
+ const issues = [];
330
+ const output = (result.stderr || "") + "\n" + (result.stdout || "");
331
+ const lineRe = /^(.+?):(\d+):\d+:\s*(.+)$/gm;
332
+ let m;
333
+ while ((m = lineRe.exec(output)) !== null) {
334
+ const file = m[1].replace(/\\/g, "/").replace(/^\.\//, "");
335
+ const line = parseInt(m[2], 10);
336
+ const detail = m[3];
337
+ if (targetFiles.length === 0 || targetFiles.some(tf => file === tf || file.endsWith("/" + tf))) {
338
+ issues.push({ file, line, kind: "type_error", detail, source: "go-deep-layer" });
339
+ }
340
+ }
341
+ return { issues };
342
+ }
343
+
344
+ // --- Main request handler ---
345
+ async function handleRequest(req) {
346
+ const root = req.root;
347
+ const files = req.files || [];
348
+ const t0 = Date.now();
349
+
350
+ if (!goplsProc && !goplsStartingPromise) {
351
+ eagerWarmUp(root);
352
+ }
353
+
354
+ // Warm path
355
+ if (goplsWarmedUp && goplsProc && goplsReady && goplsRoot === root) {
356
+ try {
357
+ const tOpen = Date.now();
358
+ const issues = await queryLsp(root, files);
359
+ const tDone = Date.now();
360
+ return { issues, elapsed_ms: tDone - t0, timing: { open_ms: tOpen - t0, diag_ms: tDone - tOpen } };
361
+ } catch (lspErr) {
362
+ const goResult = runGoBuild(root, files);
363
+ if (!goResult.error) {
364
+ goResult.elapsed_ms = Date.now() - t0;
365
+ goResult.fallback = `lsp: ${lspErr.message}`;
366
+ return goResult;
367
+ }
368
+ return { issues: [], elapsed_ms: Date.now() - t0, error: `lsp: ${lspErr.message}; go: ${goResult.error}` };
369
+ }
370
+ }
371
+
372
+ // Cold/bootstrap: race LSP vs go build
373
+ const lspPromise = (async () => {
374
+ try {
375
+ if (!goplsProc && !goplsStartingPromise) eagerWarmUp(root);
376
+ if (goplsStartingPromise) await goplsStartingPromise;
377
+ const issues = await queryLsp(root, files);
378
+ return { issues, elapsed_ms: Date.now() - t0 };
379
+ } catch {
380
+ return null;
381
+ }
382
+ })();
383
+
384
+ const goPromise = new Promise(resolve => {
385
+ setImmediate(() => {
386
+ const result = runGoBuild(root, files);
387
+ if (!result.error) {
388
+ result.elapsed_ms = Date.now() - t0;
389
+ result.bootstrap = true;
390
+ resolve(result);
391
+ } else {
392
+ resolve(null);
393
+ }
394
+ });
395
+ });
396
+
397
+ const budgetPromise = new Promise(resolve => {
398
+ setTimeout(() => resolve("timeout"), BOOTSTRAP_BUDGET_MS);
399
+ });
400
+
401
+ const race = await Promise.race([lspPromise, budgetPromise]);
402
+ if (race !== "timeout" && race !== null) return race;
403
+
404
+ const goResult = await goPromise;
405
+ if (goResult) return goResult;
406
+
407
+ const lspResult = await lspPromise;
408
+ if (lspResult) return lspResult;
409
+
410
+ return { issues: [], elapsed_ms: Date.now() - t0, error: "bootstrap: both gopls and go build failed" };
411
+ }
412
+
413
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
414
+ rl.on("line", line => {
415
+ line = line.trim();
416
+ if (!line) return;
417
+ let req;
418
+ try { req = JSON.parse(line); } catch { return; }
419
+ handleRequest(req).then(resp => {
420
+ process.stdout.write(JSON.stringify(resp) + "\n");
421
+ }).catch(e => {
422
+ process.stdout.write(JSON.stringify({ error: String(e), elapsed_ms: 0 }) + "\n");
423
+ });
424
+ });
425
+ rl.on("close", () => { killGopls(); });
426
+
427
+ process.on("exit", killGopls);