pi-lens 3.8.27 → 3.8.28
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/CHANGELOG.md +11 -0
- package/clients/biome-client.ts +14 -0
- package/clients/installer/index.ts +18 -0
- package/clients/language-policy.ts +1 -1
- package/clients/lsp/client.ts +9 -2
- package/clients/lsp/index.ts +12 -5
- package/clients/lsp/server.ts +89 -33
- package/clients/runtime-session.ts +35 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.8.28] - 2026-04-19
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Session startup no longer blocks the Node event loop** — tool availability probes (biome, ast-grep, ruff, knip, jscpd, madge) now run via async `ensureAvailable()` in a fire-and-forget IIFE instead of `setImmediate` + `spawnSync`, eliminating ~8–10 s of main-thread freeze on startup.
|
|
9
|
+
- **Biome binary lookup extended** — `getBiomeBinary()` now checks `~/.pi-lens/tools/node_modules/.bin/biome` so the async probe finds the pre-installed binary without falling back to `npx`.
|
|
10
|
+
- **CSS roots and Windows LSP shims tightened** — improved root resolution for CSS language server on Windows.
|
|
11
|
+
- **Zig compile coverage kept active** — LSP availability check no longer incorrectly disables Zig compile diagnostics.
|
|
12
|
+
- **Ruby LSP startup budgets relaxed** — reduced false-negative LSP attach failures on slower machines.
|
|
13
|
+
- **Kotlin and Zig LSP availability improved** — more reliable server detection across platforms.
|
|
14
|
+
- **Standalone Python and Ruby LSP roots fixed** — correct workspace root used when opening files outside a project directory.
|
|
15
|
+
|
|
5
16
|
## [3.8.27] - 2026-04-19
|
|
6
17
|
|
|
7
18
|
### Added
|
package/clients/biome-client.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
|
+
import * as os from "node:os";
|
|
12
13
|
import * as path from "node:path";
|
|
13
14
|
import { isFileKind } from "./file-kinds.js";
|
|
14
15
|
import { safeSpawn, safeSpawnAsync } from "./safe-spawn.js";
|
|
@@ -60,16 +61,29 @@ export class BiomeClient {
|
|
|
60
61
|
if (this.localBinaryPath) return { cmd: this.localBinaryPath, args: [] };
|
|
61
62
|
|
|
62
63
|
// Walk up from cwd looking for node_modules/.bin/biome.
|
|
64
|
+
// Also check ~/.pi-lens/tools (where ensureTool("biome") auto-installs),
|
|
65
|
+
// so we avoid the ~1.5s `npx @biomejs/biome --version` fallback when
|
|
66
|
+
// the tool is already installed but not in the project's node_modules.
|
|
63
67
|
// On Windows prefer .cmd (native batch) over the sh wrapper — 2x faster.
|
|
64
68
|
const isWin = process.platform === "win32";
|
|
69
|
+
const piLensBin = path.join(
|
|
70
|
+
os.homedir(),
|
|
71
|
+
".pi-lens",
|
|
72
|
+
"tools",
|
|
73
|
+
"node_modules",
|
|
74
|
+
".bin",
|
|
75
|
+
);
|
|
65
76
|
const candidates = isWin
|
|
66
77
|
? [
|
|
67
78
|
path.join(process.cwd(), "node_modules", ".bin", "biome.cmd"),
|
|
68
79
|
path.join(process.cwd(), "node_modules", ".bin", "biome"),
|
|
80
|
+
path.join(piLensBin, "biome.cmd"),
|
|
81
|
+
path.join(piLensBin, "biome"),
|
|
69
82
|
]
|
|
70
83
|
: [
|
|
71
84
|
path.join(process.cwd(), "node_modules", ".bin", "biome"),
|
|
72
85
|
path.join(process.cwd(), "node_modules", ".bin", "biome.cmd"),
|
|
86
|
+
path.join(piLensBin, "biome"),
|
|
73
87
|
];
|
|
74
88
|
for (const p of candidates) {
|
|
75
89
|
if (fs.existsSync(p)) {
|
|
@@ -524,6 +524,24 @@ const TOOLS: ToolDefinition[] = [
|
|
|
524
524
|
binaryInArchive: "terraform-ls",
|
|
525
525
|
},
|
|
526
526
|
},
|
|
527
|
+
{
|
|
528
|
+
id: "zls",
|
|
529
|
+
name: "zls",
|
|
530
|
+
checkCommand: "zls",
|
|
531
|
+
checkArgs: ["--version"],
|
|
532
|
+
installStrategy: "github",
|
|
533
|
+
binaryName: "zls",
|
|
534
|
+
github: {
|
|
535
|
+
repo: "zigtools/zls",
|
|
536
|
+
assetMatch: (platform, arch) => {
|
|
537
|
+
if (platform === "linux") return arch === "arm64" ? "aarch64-linux.tar.xz" : "x86_64-linux.tar.xz";
|
|
538
|
+
if (platform === "darwin") return arch === "arm64" ? "aarch64-macos.tar.xz" : "x86_64-macos.tar.xz";
|
|
539
|
+
if (platform === "win32") return arch === "arm64" ? "aarch64-windows.zip" : "x86_64-windows.zip";
|
|
540
|
+
return undefined;
|
|
541
|
+
},
|
|
542
|
+
binaryInArchive: "zls",
|
|
543
|
+
},
|
|
544
|
+
},
|
|
527
545
|
];
|
|
528
546
|
|
|
529
547
|
const ensureInFlight = new Map<string, Promise<string | undefined>>();
|
|
@@ -154,7 +154,7 @@ const PRIMARY_DISPATCH_GROUPS: Partial<Record<FileKind, RunnerGroup>> = {
|
|
|
154
154
|
swift: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["swift"] },
|
|
155
155
|
dart: { mode: "fallback", runnerIds: ["lsp", "dart-analyze"], filterKinds: ["dart"] },
|
|
156
156
|
lua: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["lua"] },
|
|
157
|
-
zig: { mode: "
|
|
157
|
+
zig: { mode: "all", runnerIds: ["lsp", "zig-check"], filterKinds: ["zig"] },
|
|
158
158
|
haskell: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["haskell"] },
|
|
159
159
|
elixir: { mode: "fallback", runnerIds: ["lsp", "elixir-check", "credo"], filterKinds: ["elixir"] },
|
|
160
160
|
gleam: { mode: "fallback", runnerIds: ["lsp", "gleam-check"], filterKinds: ["gleam"] },
|
package/clients/lsp/client.ts
CHANGED
|
@@ -561,10 +561,17 @@ export async function createLSPClient(options: {
|
|
|
561
561
|
process: LSPProcess;
|
|
562
562
|
root: string;
|
|
563
563
|
initialization?: Record<string, unknown>;
|
|
564
|
+
initializeTimeoutMs?: number;
|
|
564
565
|
}): Promise<LSPClientInfo> {
|
|
565
566
|
installCrashGuard();
|
|
566
567
|
|
|
567
|
-
const {
|
|
568
|
+
const {
|
|
569
|
+
serverId,
|
|
570
|
+
process: lspProcess,
|
|
571
|
+
root,
|
|
572
|
+
initialization,
|
|
573
|
+
initializeTimeoutMs = INITIALIZE_TIMEOUT_MS,
|
|
574
|
+
} = options;
|
|
568
575
|
|
|
569
576
|
const startupState: {
|
|
570
577
|
exitCode: number | null;
|
|
@@ -683,7 +690,7 @@ export async function createLSPClient(options: {
|
|
|
683
690
|
},
|
|
684
691
|
initializationOptions: initialization,
|
|
685
692
|
}),
|
|
686
|
-
|
|
693
|
+
initializeTimeoutMs,
|
|
687
694
|
);
|
|
688
695
|
} finally {
|
|
689
696
|
(lspProcess.stderr as NodeJS.ReadableStream).off("data", onStartupStderr);
|
package/clients/lsp/index.ts
CHANGED
|
@@ -133,8 +133,14 @@ export class LSPService {
|
|
|
133
133
|
filePath: string,
|
|
134
134
|
maxWaitMs?: number,
|
|
135
135
|
): Promise<SpawnedServer | undefined> {
|
|
136
|
-
const withBudget = async (): Promise<SpawnedServer | undefined> => {
|
|
137
136
|
const servers = getServersForFileWithConfig(filePath);
|
|
137
|
+
const serverWaitOverrideMs = servers.reduce(
|
|
138
|
+
(max, server) => Math.max(max, server.clientWaitTimeoutMs ?? 0),
|
|
139
|
+
0,
|
|
140
|
+
);
|
|
141
|
+
const effectiveMaxWaitMs = Math.max(maxWaitMs ?? 0, serverWaitOverrideMs);
|
|
142
|
+
|
|
143
|
+
const withBudget = async (): Promise<SpawnedServer | undefined> => {
|
|
138
144
|
if (servers.length === 0) return undefined;
|
|
139
145
|
|
|
140
146
|
// Try each matching server
|
|
@@ -169,14 +175,14 @@ export class LSPService {
|
|
|
169
175
|
return undefined;
|
|
170
176
|
};
|
|
171
177
|
|
|
172
|
-
if (!
|
|
178
|
+
if (!effectiveMaxWaitMs || effectiveMaxWaitMs <= 0) {
|
|
173
179
|
return withBudget();
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
const timeoutResult = await Promise.race<SpawnedServer | undefined>([
|
|
177
183
|
withBudget(),
|
|
178
184
|
new Promise<undefined>((resolve) =>
|
|
179
|
-
setTimeout(() => resolve(undefined),
|
|
185
|
+
setTimeout(() => resolve(undefined), effectiveMaxWaitMs),
|
|
180
186
|
),
|
|
181
187
|
]);
|
|
182
188
|
|
|
@@ -185,9 +191,9 @@ export class LSPService {
|
|
|
185
191
|
type: "phase",
|
|
186
192
|
phase: "lsp_client_wait_timeout",
|
|
187
193
|
filePath,
|
|
188
|
-
durationMs:
|
|
194
|
+
durationMs: effectiveMaxWaitMs,
|
|
189
195
|
metadata: {
|
|
190
|
-
maxWaitMs,
|
|
196
|
+
maxWaitMs: effectiveMaxWaitMs,
|
|
191
197
|
},
|
|
192
198
|
});
|
|
193
199
|
}
|
|
@@ -315,6 +321,7 @@ export class LSPService {
|
|
|
315
321
|
process: spawned.process,
|
|
316
322
|
root,
|
|
317
323
|
initialization: spawned.initialization,
|
|
324
|
+
initializeTimeoutMs: server.initializeTimeoutMs,
|
|
318
325
|
});
|
|
319
326
|
const wsDiag =
|
|
320
327
|
typeof client.getWorkspaceDiagnosticsSupport === "function"
|
package/clients/lsp/server.ts
CHANGED
|
@@ -32,6 +32,17 @@ export interface LSPServerInfo {
|
|
|
32
32
|
name: string;
|
|
33
33
|
extensions: string[];
|
|
34
34
|
root: RootFunction;
|
|
35
|
+
/**
|
|
36
|
+
* Optional per-server initialize timeout.
|
|
37
|
+
* Useful for servers like Ruby LSP that do real project bootstrap work
|
|
38
|
+
* before they can answer initialize.
|
|
39
|
+
*/
|
|
40
|
+
initializeTimeoutMs?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Optional per-server wait budget for navigation requests that need a client
|
|
43
|
+
* to become ready first.
|
|
44
|
+
*/
|
|
45
|
+
clientWaitTimeoutMs?: number;
|
|
35
46
|
spawn(
|
|
36
47
|
root: string,
|
|
37
48
|
options?: LSPSpawnOptions,
|
|
@@ -285,15 +296,28 @@ function nodeBinCandidates(root: string, baseName: string): string[] {
|
|
|
285
296
|
if (process.platform === "win32") {
|
|
286
297
|
return [
|
|
287
298
|
`${localBase}.cmd`,
|
|
288
|
-
`${localBase}.ps1`,
|
|
289
299
|
`${localBase}.exe`,
|
|
290
|
-
localBase,
|
|
291
300
|
baseName,
|
|
292
301
|
];
|
|
293
302
|
}
|
|
294
303
|
return [localBase, baseName];
|
|
295
304
|
}
|
|
296
305
|
|
|
306
|
+
function normalizeRootKey(root: string): string {
|
|
307
|
+
return process.platform === "win32"
|
|
308
|
+
? path.resolve(root).toLowerCase()
|
|
309
|
+
: path.resolve(root);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function IgnoreHomeRoot(primary: RootFunction): RootFunction {
|
|
313
|
+
const homeKey = normalizeRootKey(os.homedir());
|
|
314
|
+
return async (file: string): Promise<string | undefined> => {
|
|
315
|
+
const root = await primary(file);
|
|
316
|
+
if (!root) return undefined;
|
|
317
|
+
return normalizeRootKey(root) === homeKey ? undefined : root;
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
297
321
|
function rubyBinCandidates(baseName: string): string[] {
|
|
298
322
|
const candidates: string[] = [];
|
|
299
323
|
const userProfile = process.env.USERPROFILE;
|
|
@@ -729,15 +753,17 @@ export const PythonServer: LSPServerInfo = {
|
|
|
729
753
|
id: "python",
|
|
730
754
|
name: "Pyright Language Server",
|
|
731
755
|
extensions: [".py", ".pyi"],
|
|
732
|
-
root:
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
756
|
+
root: RootWithFallback(
|
|
757
|
+
createRootDetector([
|
|
758
|
+
".git",
|
|
759
|
+
"pyproject.toml",
|
|
760
|
+
"setup.py",
|
|
761
|
+
"setup.cfg",
|
|
762
|
+
"requirements.txt",
|
|
763
|
+
"Pipfile",
|
|
764
|
+
"poetry.lock",
|
|
765
|
+
]),
|
|
766
|
+
),
|
|
741
767
|
async spawn(root, options) {
|
|
742
768
|
const path = await import("node:path");
|
|
743
769
|
const fs = await import("node:fs/promises");
|
|
@@ -838,15 +864,17 @@ export const PythonPylspServer: LSPServerInfo = {
|
|
|
838
864
|
id: "python-pylsp",
|
|
839
865
|
name: "Python LSP Server (pylsp)",
|
|
840
866
|
extensions: [".py", ".pyi"],
|
|
841
|
-
root:
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
867
|
+
root: RootWithFallback(
|
|
868
|
+
createRootDetector([
|
|
869
|
+
".git",
|
|
870
|
+
"pyproject.toml",
|
|
871
|
+
"setup.py",
|
|
872
|
+
"setup.cfg",
|
|
873
|
+
"requirements.txt",
|
|
874
|
+
"Pipfile",
|
|
875
|
+
"poetry.lock",
|
|
876
|
+
]),
|
|
877
|
+
),
|
|
850
878
|
async spawn(root) {
|
|
851
879
|
try {
|
|
852
880
|
const proc = await launchLSP("pylsp", [], { cwd: root });
|
|
@@ -909,7 +937,11 @@ export const RubyServer: LSPServerInfo = {
|
|
|
909
937
|
id: "ruby",
|
|
910
938
|
name: "Ruby LSP",
|
|
911
939
|
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
|
912
|
-
root: PriorityRoot([["Gemfile", ".ruby-version"], [".git"]]),
|
|
940
|
+
root: RootWithFallback(PriorityRoot([["Gemfile", ".ruby-version"], [".git"]])),
|
|
941
|
+
// Ruby LSP may need extra time to finish composed-bundle setup before it can
|
|
942
|
+
// answer initialize/documentSymbol on cold start.
|
|
943
|
+
initializeTimeoutMs: 30_000,
|
|
944
|
+
clientWaitTimeoutMs: 30_000,
|
|
913
945
|
async spawn(root, options) {
|
|
914
946
|
// Try ruby-lsp first, then solargraph, then rubocop --lsp
|
|
915
947
|
// Each has different args so we can't use a single resolveAndLaunch call
|
|
@@ -947,7 +979,7 @@ export const RubySolargraphServer: LSPServerInfo = {
|
|
|
947
979
|
id: "ruby-solargraph",
|
|
948
980
|
name: "Solargraph",
|
|
949
981
|
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
|
950
|
-
root: PriorityRoot([["Gemfile", ".ruby-version"], [".git"]]),
|
|
982
|
+
root: RootWithFallback(PriorityRoot([["Gemfile", ".ruby-version"], [".git"]])),
|
|
951
983
|
async spawn(root) {
|
|
952
984
|
for (const command of ["solargraph", ...rubyBinCandidates("solargraph")]) {
|
|
953
985
|
try {
|
|
@@ -1028,14 +1060,24 @@ export const JavaServer = createInteractiveServer({
|
|
|
1028
1060
|
command: () => process.env.JDTLS_PATH || "jdtls",
|
|
1029
1061
|
});
|
|
1030
1062
|
|
|
1031
|
-
export const KotlinServer =
|
|
1063
|
+
export const KotlinServer: LSPServerInfo = {
|
|
1032
1064
|
id: "kotlin",
|
|
1033
1065
|
name: "Kotlin Language Server",
|
|
1034
1066
|
extensions: [".kt", ".kts"],
|
|
1035
1067
|
root: RootWithFallback(createRootDetector(["build.gradle.kts", "build.gradle", "pom.xml"])),
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1068
|
+
async spawn(root, options) {
|
|
1069
|
+
// Prefer the newer official Kotlin LSP CLI when available, but keep
|
|
1070
|
+
// compatibility with the older fwcd kotlin-language-server command.
|
|
1071
|
+
return resolveAndLaunch(
|
|
1072
|
+
{
|
|
1073
|
+
candidates: ["kotlin-lsp", "kotlin-language-server"],
|
|
1074
|
+
args: [],
|
|
1075
|
+
cwd: root,
|
|
1076
|
+
},
|
|
1077
|
+
options?.allowInstall,
|
|
1078
|
+
);
|
|
1079
|
+
},
|
|
1080
|
+
};
|
|
1039
1081
|
|
|
1040
1082
|
export const SwiftServer = createInteractiveServer({
|
|
1041
1083
|
id: "swift",
|
|
@@ -1080,14 +1122,23 @@ export const CppServer = createInteractiveServer({
|
|
|
1080
1122
|
args: ["--background-index"],
|
|
1081
1123
|
});
|
|
1082
1124
|
|
|
1083
|
-
export const ZigServer =
|
|
1125
|
+
export const ZigServer: LSPServerInfo = {
|
|
1084
1126
|
id: "zig",
|
|
1085
1127
|
name: "ZLS",
|
|
1086
1128
|
extensions: [".zig", ".zon"],
|
|
1087
1129
|
root: RootWithFallback(createRootDetector(["build.zig"])),
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1130
|
+
spawn(root, options) {
|
|
1131
|
+
return resolveAndLaunch(
|
|
1132
|
+
{
|
|
1133
|
+
candidates: ["zls"],
|
|
1134
|
+
args: [],
|
|
1135
|
+
cwd: root,
|
|
1136
|
+
managedToolId: "zls",
|
|
1137
|
+
},
|
|
1138
|
+
options?.allowInstall,
|
|
1139
|
+
);
|
|
1140
|
+
},
|
|
1141
|
+
};
|
|
1091
1142
|
|
|
1092
1143
|
export const HaskellServer = createInteractiveServer({
|
|
1093
1144
|
id: "haskell",
|
|
@@ -1230,7 +1281,9 @@ export const HtmlServer: LSPServerInfo = {
|
|
|
1230
1281
|
id: "html",
|
|
1231
1282
|
name: "VSCode HTML Language Server",
|
|
1232
1283
|
extensions: [".html", ".htm"],
|
|
1233
|
-
root:
|
|
1284
|
+
root: RootWithFallback(
|
|
1285
|
+
IgnoreHomeRoot(PriorityRoot([["package.json", "index.html", "vite.config.ts"]])),
|
|
1286
|
+
),
|
|
1234
1287
|
spawn(root, options) {
|
|
1235
1288
|
return resolveAndLaunch(
|
|
1236
1289
|
{ candidates: nodeBinCandidates(root, "vscode-html-language-server"), args: ["--stdio"], cwd: root, managedToolId: "vscode-html-languageserver-bin" },
|
|
@@ -1334,7 +1387,11 @@ export const CssServer: LSPServerInfo = {
|
|
|
1334
1387
|
id: "css",
|
|
1335
1388
|
name: "CSS Language Server",
|
|
1336
1389
|
extensions: [".css", ".scss", ".sass", ".less"],
|
|
1337
|
-
root:
|
|
1390
|
+
root: RootWithFallback(
|
|
1391
|
+
IgnoreHomeRoot(
|
|
1392
|
+
PriorityRoot([["package.json", "postcss.config.js", "tailwind.config.js", "vite.config.ts"]]),
|
|
1393
|
+
),
|
|
1394
|
+
),
|
|
1338
1395
|
spawn(root, options) {
|
|
1339
1396
|
return resolveAndLaunch(
|
|
1340
1397
|
{ candidates: nodeBinCandidates(root, "vscode-css-language-server"), args: ["--stdio"], cwd: root, managedToolId: "vscode-css-languageserver" },
|
|
@@ -1353,7 +1410,6 @@ export const LSP_SERVERS: LSPServerInfo[] = [
|
|
|
1353
1410
|
GoServer,
|
|
1354
1411
|
RustServer,
|
|
1355
1412
|
RubyServer,
|
|
1356
|
-
RubySolargraphServer,
|
|
1357
1413
|
PHPServer,
|
|
1358
1414
|
// PowerShellServer — not included; no viable LSP binary, coverage notice fires instead
|
|
1359
1415
|
CSharpServer,
|
|
@@ -376,7 +376,9 @@ function runErrorDebtBaseline(
|
|
|
376
376
|
SessionStartDeps,
|
|
377
377
|
"testRunnerClient" | "cacheManager" | "notify" | "dbg" | "runtime"
|
|
378
378
|
>,
|
|
379
|
-
detectedRunner: ReturnType<
|
|
379
|
+
detectedRunner: ReturnType<
|
|
380
|
+
SessionStartDeps["testRunnerClient"]["detectRunner"]
|
|
381
|
+
>,
|
|
380
382
|
analysisRoot: string,
|
|
381
383
|
allowBootstrapTasks: boolean,
|
|
382
384
|
getFlag: SessionStartDeps["getFlag"],
|
|
@@ -499,20 +501,38 @@ export async function handleSessionStart(
|
|
|
499
501
|
const tools: string[] = [];
|
|
500
502
|
if (getFlag("lens-lsp") && !getFlag("no-lsp")) tools.push("LSP Service");
|
|
501
503
|
|
|
502
|
-
// Warm
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
504
|
+
// Warm tool availability caches off the critical startup path. The previous
|
|
505
|
+
// version used `setImmediate` + sync `isAvailable()`, which still blocked
|
|
506
|
+
// the Node event loop (each `isAvailable()` runs `spawnSync` — and six of
|
|
507
|
+
// the seven probes fall back to `npx <tool> --version` at ~1.5-2s each,
|
|
508
|
+
// summing to ~8-10s of main-thread freeze during session_start).
|
|
509
|
+
//
|
|
510
|
+
// We now run each probe through the client's async `ensureAvailable()`
|
|
511
|
+
// (which uses a fast bare-name PATH probe, falling back to `ensureTool`
|
|
512
|
+
// async install) inside a fire-and-forget IIFE. No main-thread blocking.
|
|
513
|
+
//
|
|
514
|
+
// Notes:
|
|
515
|
+
// - `typeCoverageClient` has no async probe and is only used by
|
|
516
|
+
// `/lens-booboo`, so we let it probe lazily when first needed.
|
|
517
|
+
// - `ensureAvailable()` can auto-install missing tools into `~/.pi-lens/tools`.
|
|
518
|
+
// This matches `firePreinstallDefaults`' existing behaviour for biome /
|
|
519
|
+
// typescript-language-server.
|
|
520
|
+
void (async () => {
|
|
521
|
+
const warmStart = Date.now();
|
|
522
|
+
const [biomeReady, sgReady, ruffReady] = await Promise.all([
|
|
523
|
+
biomeClient.ensureAvailable().catch(() => false),
|
|
524
|
+
astGrepClient.ensureAvailable().catch(() => false),
|
|
525
|
+
ruffClient.ensureAvailable().catch(() => false),
|
|
526
|
+
]);
|
|
527
|
+
await Promise.allSettled([
|
|
528
|
+
knipClient.ensureAvailable().catch(() => false),
|
|
529
|
+
depChecker.ensureAvailable().catch(() => false),
|
|
530
|
+
jscpdClient.ensureAvailable().catch(() => false),
|
|
531
|
+
]);
|
|
532
|
+
dbg(
|
|
533
|
+
`session_start tools (deferred probes complete, ${Date.now() - warmStart}ms): biome=${biomeReady} ast-grep=${sgReady} ruff=${ruffReady}`,
|
|
534
|
+
);
|
|
535
|
+
})();
|
|
516
536
|
|
|
517
537
|
if (allowBootstrapTasks && getFlag("lens-lsp") && !getFlag("no-lsp")) {
|
|
518
538
|
const cleaned = cleanStaleTsBuildInfo(ctxCwd ?? process.cwd());
|