UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

600 lines (598 loc) • 21.7 kB
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url); import { releaseManagedToolRefreshSlot, reserveManagedToolRefreshSlot } from "./chunk-UXVNL3D7.js"; import { HOOK_WALL_BUDGET_MS, bounded } from "./chunk-GUOGVTCQ.js"; import { acquireManagedInstallGate, commitDurableStoreAsync, getManagedToolsDir, getRefreshableManagedTools, invalidateManagedToolResolution, isManagedToolPresent, logSessionStart, npmToolNeedsPostinstall, refreshManagedTool, resolveManagedNpmBinPath, verifyToolBinary } from "./chunk-OQGFCZJ4.js"; import "./chunk-RUOG5D2C.js"; import { pmBinary, resolveNodePackageManager, updateArgs } from "./chunk-U3VGR4VU.js"; import "./chunk-NTUJEGDZ.js"; import "./chunk-EOKRQ3SA.js"; import "./chunk-WONERJTP.js"; import "./chunk-22AVIGXS.js"; import "./chunk-GVXA6CCE.js"; import "./chunk-6QIUX4EP.js"; import "./chunk-VHQLPIRJ.js"; import { safeSpawnAsync } from "./chunk-VN2AXLEX.js"; import "./chunk-BW5KFIQC.js"; import "./chunk-QYF5S6P6.js"; import "./chunk-5ZEJHV35.js"; import "./chunk-DQ2NZ7C4.js"; import "./chunk-VNMT7C64.js"; import "./chunk-2EECBNC5.js"; import { recordDegradationOnce } from "./chunk-N3YQJI6O.js"; import "./chunk-O6TQT6RI.js"; import "./chunk-5THXD6X5.js"; import "./chunk-UK3CMEAL.js"; import "./chunk-LJDODBBZ.js"; import "./chunk-ABBOA7UD.js"; import "./chunk-Q5U6FMDI.js"; import "./chunk-NXL4FFQQ.js"; // dist/clients/installer/managed-tool-refresh.js import * as fs from "node:fs/promises"; import * as path from "node:path"; var STATE_FILENAME = ".refresh-state.json"; var STATE_VERSION = 1; var DEFAULT_INTERVAL_MS = 7 * 24 * 60 * 60 * 1e3; var DEFAULT_RETRY_INTERVAL_MS = 24 * 60 * 60 * 1e3; var DEFAULT_MAX_PER_SESSION = 1; var DEFAULT_TIMEOUT_MS = 12e4; function numericEnv(name, fallback) { const raw = process.env[name]; if (raw === void 0) return fallback; const parsed = Number(raw); return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback; } function refreshIntervalMs() { return numericEnv("PI_LENS_TOOL_REFRESH_INTERVAL_MS", DEFAULT_INTERVAL_MS); } function retryIntervalMs() { return numericEnv("PI_LENS_TOOL_REFRESH_RETRY_MS", DEFAULT_RETRY_INTERVAL_MS); } function maxPerSession() { return numericEnv("PI_LENS_TOOL_REFRESH_MAX_PER_SESSION", DEFAULT_MAX_PER_SESSION); } function refreshTimeoutMs() { return numericEnv("PI_LENS_INSTALL_TIMEOUT_MS", DEFAULT_TIMEOUT_MS); } function emptyState() { return { version: STATE_VERSION, tools: {} }; } function getManagedToolRefreshStatePath() { return path.join(getManagedToolsDir(), STATE_FILENAME); } function parseEntry(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { return void 0; } const candidate = value; if (typeof candidate.checkedAt !== "number") return void 0; if (!Number.isFinite(candidate.checkedAt)) return void 0; return { checkedAt: candidate.checkedAt, ...typeof candidate.version === "string" && { version: candidate.version }, ...typeof candidate.resolutionId === "string" && { resolutionId: candidate.resolutionId }, ...typeof candidate.etag === "string" && { etag: candidate.etag }, ...candidate.failed === true && { failed: true } }; } function parseState(contents) { const parsed = JSON.parse(contents); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("refresh-state root is not an object"); } const rawTools = parsed.tools; if (!rawTools || typeof rawTools !== "object" || Array.isArray(rawTools)) { throw new Error("refresh-state.tools is not an object"); } const tools = {}; for (const [toolId, value] of Object.entries(rawTools)) { const entry = parseEntry(value); if (entry) tools[toolId] = entry; } return { version: STATE_VERSION, tools }; } async function readManagedToolRefreshState() { let contents; try { contents = await fs.readFile(getManagedToolRefreshStatePath(), "utf-8"); } catch (err) { const code = err.code; if (code === "ENOENT") return { status: "empty", state: emptyState() }; return { status: "unreadable", reason: code ?? "read error" }; } try { return { status: "ok", state: parseState(contents) }; } catch (err) { return { status: "unreadable", reason: err.message }; } } async function writeRefreshStamp(toolId, entry) { try { await fs.mkdir(getManagedToolsDir(), { recursive: true }); await commitDurableStoreAsync({ path: getManagedToolRefreshStatePath(), deserialize: (contents) => { if (contents === void 0) return emptyState(); try { return parseState(contents); } catch { return emptyState(); } }, merge: (disk) => { disk.tools[toolId] = entry; return disk; }, serialize: (disk) => JSON.stringify(disk, null, 2), waitMs: 2e3, retryMs: 25, staleMs: 6e4, timeoutMessage: "Timed out waiting for managed-tool refresh-state lock", onContention: "skip-log", logContention: () => { logSessionStart(`managed-tool-refresh ${toolId}: stamp deferred because another process owns the lock`); } }); } catch (err) { recordDegradationOnce({ kind: "managed-tool-refresh", subject: toolId, reason: `stamp write failed: ${err.message}` }); logSessionStart(`managed-tool-refresh ${toolId}: stamp write failed (${err.message})`); } } async function stampManagedToolInstall(toolId, resolutionId, now = Date.now()) { await writeRefreshStamp(toolId, { checkedAt: now, ...resolutionId !== void 0 && { resolutionId } }); } async function stampManagedToolInstalled(toolId, packageName, now = Date.now()) { const version = await readInstalledVersion(packageName); await writeRefreshStamp(toolId, { checkedAt: now, ...version !== void 0 && { version } }); } async function installedRefreshCandidates() { const candidates = []; for (const tool of getRefreshableManagedTools()) { if (!await isManagedToolPresent(tool.toolId)) continue; candidates.push({ toolId: tool.toolId, strategy: tool.strategy, checkArgs: tool.checkArgs, verificationTimeoutMs: tool.verificationTimeoutMs ?? 1e4, ...tool.packageName !== void 0 && { packageName: tool.packageName }, ...tool.binaryName !== void 0 && { binaryName: tool.binaryName }, ...tool.packageEntryOf !== void 0 && { packageEntryOf: tool.packageEntryOf } }); } const byPackage = /* @__PURE__ */ new Map(); const distinct = []; for (const candidate of candidates) { if (candidate.strategy !== "npm" || !candidate.packageName) { distinct.push(candidate); continue; } const existing = byPackage.get(candidate.packageName); if (existing) { existing.coveredToolIds?.push(candidate.toolId); continue; } const representative = { ...candidate, coveredToolIds: [candidate.toolId] }; byPackage.set(candidate.packageName, representative); distinct.push(representative); } return distinct; } function selectStaleTools(candidates, state, now) { const interval = refreshIntervalMs(); const retry = retryIntervalMs(); const due = candidates.filter((candidate) => { return (candidate.coveredToolIds ?? [candidate.toolId]).some((toolId) => { const entry = state.tools[toolId]; if (!entry) return true; const cooldown = entry.failed ? retry : interval; return now - entry.checkedAt >= cooldown; }); }); return due.sort((a, b) => { const aAt = Math.min(...(a.coveredToolIds ?? [a.toolId]).map((toolId) => state.tools[toolId]?.checkedAt ?? 0)); const bAt = Math.min(...(b.coveredToolIds ?? [b.toolId]).map((toolId) => state.tools[toolId]?.checkedAt ?? 0)); return aAt !== bAt ? aAt - bAt : a.toolId.localeCompare(b.toolId); }); } async function readInstalledVersion(packageName) { try { const raw = await fs.readFile(path.join(getManagedToolsDir(), "node_modules", packageName, "package.json"), "utf-8"); const parsed = JSON.parse(raw); const version = parsed?.version; return typeof version === "string" ? version : void 0; } catch { return void 0; } } async function refreshOne(candidate, entry, now) { return candidate.strategy === "npm" ? refreshNpmOne(candidate, now) : refreshNonNpmOne(candidate, entry, now); } async function refreshNonNpmOne(candidate, entry, now) { const previousVersion = entry?.version; const startedAt = Date.now(); const attempt = await refreshManagedTool(candidate.toolId, { ...entry?.resolutionId !== void 0 && { resolutionId: entry.resolutionId }, ...entry?.etag !== void 0 && { etag: entry.etag }, ...previousVersion !== void 0 && { version: previousVersion } }); const elapsedMs = Date.now() - startedAt; if (!attempt.ok) { if (attempt.declined) { logSessionStart(`managed-tool-refresh ${candidate.toolId}: ${candidate.strategy} refresh declined after ${elapsedMs}ms (${attempt.reason ?? "unknown"}) \u2014 no stamp written, retried next session`); return { toolId: candidate.toolId, strategy: candidate.strategy, ...candidate.packageName !== void 0 && { packageName: candidate.packageName }, previousVersion, currentVersion: previousVersion, changed: false, verified: false, ok: false, attempted: false }; } invalidateManagedToolResolution(candidate.toolId); recordDegradationOnce({ kind: "managed-tool-refresh", subject: candidate.toolId, reason: `${candidate.strategy} refresh failed: ${attempt.reason ?? "unknown"}` }); logSessionStart(`managed-tool-refresh ${candidate.toolId}: ${candidate.strategy} refresh failed after ${elapsedMs}ms \u2014 keeping ${previousVersion ?? "installed"} (${attempt.reason ?? "unknown"})`); await writeRefreshStamp(candidate.toolId, { checkedAt: now, failed: true, ...previousVersion !== void 0 && { version: previousVersion }, ...entry?.resolutionId !== void 0 && { resolutionId: entry.resolutionId } // Deliberately NOT carrying `entry.etag` forward: a stamp that replays // the validator of a run whose download failed would get a 304 next // time and skip the install that never happened. }); return { toolId: candidate.toolId, strategy: candidate.strategy, ...candidate.packageName !== void 0 && { packageName: candidate.packageName }, previousVersion, currentVersion: previousVersion, changed: false, // No spawn to verify for the non-npm strategies — a failed refresh never // touched the installed copy, so there is nothing new to check. verified: false, ok: false, attempted: true }; } const currentVersion = attempt.version; const changed = !attempt.unchanged; await writeRefreshStamp(candidate.toolId, { checkedAt: now, ...currentVersion !== void 0 && { version: currentVersion }, ...attempt.resolutionId !== void 0 && { resolutionId: attempt.resolutionId }, ...attempt.etag !== void 0 && { etag: attempt.etag } }); logSessionStart(changed ? `managed-tool-refresh ${candidate.toolId}: ${previousVersion ?? "unknown"} \u2192 ${currentVersion ?? "unknown"} via ${candidate.strategy} (${elapsedMs}ms)` : `managed-tool-refresh ${candidate.toolId}: unchanged at ${currentVersion ?? previousVersion ?? "unknown"} via ${candidate.strategy} (${elapsedMs}ms)`); return { toolId: candidate.toolId, strategy: candidate.strategy, ...candidate.packageName !== void 0 && { packageName: candidate.packageName }, previousVersion, currentVersion, changed, // `refreshManagedTool` already verifies the artifact it just wrote // (`verifyRefreshedArtifact` for github/archive/maven, the `--version` // probe for pip/gem) — `ok: true` here already means "and it runs". verified: true, ok: true, attempted: true }; } async function refreshNpmOne(candidate, now) { const packageName = candidate.packageName; const gateStartedAt = Date.now(); const gate = await acquireManagedInstallGate(`managed tool refresh: ${candidate.toolId}`); if (!gate.ok) { logSessionStart(`managed-tool-refresh ${candidate.toolId}: npm refresh declined after ${Date.now() - gateStartedAt}ms (${gate.reason ?? "unknown"}) \u2014 no stamp written, retried next session`); return { toolId: candidate.toolId, strategy: "npm", packageName, changed: false, verified: false, ok: false, attempted: false }; } try { return await performNpmRefresh(candidate, packageName, now); } finally { await gate.release?.(); } } async function performNpmRefresh(candidate, packageName, now) { const toolsDir = getManagedToolsDir(); const previousVersion = await readInstalledVersion(packageName); const pm = await resolveNodePackageManager(toolsDir); const testNpmScript = process.env.PI_LENS_TEST_MODE === "1" ? process.env.PI_LENS_TEST_NPM_SCRIPT : void 0; const command = testNpmScript ? process.execPath : pmBinary(pm); const args = [ ...testNpmScript ? [testNpmScript] : [], ...updateArgs(pm, packageName, { ignoreScripts: !npmToolNeedsPostinstall(packageName) }) ]; const startedAt = Date.now(); const spawned = await safeSpawnAsync(command, args, { cwd: toolsDir, timeout: refreshTimeoutMs(), ignoreAmbientSignal: true, lifetimeCoupled: true }); const elapsedMs = Date.now() - startedAt; if (spawned.status !== 0 || spawned.error) { const reason = (spawned.error?.message ?? spawned.stderr ?? "").slice(0, 200); const coveredIds = candidate.coveredToolIds?.join(",") ?? candidate.toolId; const onDisk = await readInstalledVersion(packageName); invalidateManagedToolResolution(candidate.toolId); recordDegradationOnce({ kind: "managed-tool-refresh", subject: candidate.toolId, reason: `package ${packageName}, covered ids ${coveredIds}; ${pm} update failed: ${reason || "non-zero exit"}` }); logSessionStart(`managed-tool-refresh ${candidate.toolId}: ${pm} update failed after ${elapsedMs}ms \u2014 package ${packageName}, covered ids ${coveredIds}; on disk now ${onDisk ?? "unreadable"} (was ${previousVersion ?? "unknown"}); resolution cache cleared for re-probe (${reason || "non-zero exit"})`); await writeRefreshStamp(candidate.toolId, { checkedAt: now, failed: true, ...onDisk !== void 0 && { version: onDisk } }); return { toolId: candidate.toolId, strategy: "npm", packageName, previousVersion, currentVersion: onDisk, changed: onDisk !== void 0 && onDisk !== previousVersion, verified: false, ok: false, attempted: true }; } const currentVersion = await readInstalledVersion(packageName); const changed = currentVersion !== void 0 && currentVersion !== previousVersion; invalidateManagedToolResolution(candidate.toolId); const binaryName = candidate.binaryName; const binPath = resolveManagedNpmBinPath(binaryName); const verified = await verifyToolBinary( binPath, void 0, void 0, candidate.verificationTimeoutMs, candidate.checkArgs, // #2722: the same verification the install used. Without this the daily // refresh would demand a `--version` verdict from a server that can never // give one — a ~4 MB doomed spawn that stamps the refresh failed forever. candidate.packageEntryOf ); if (!verified) { const coveredIds = candidate.coveredToolIds?.join(",") ?? candidate.toolId; recordDegradationOnce({ kind: "managed-tool-refresh", subject: candidate.toolId, reason: `package ${packageName}, covered ids ${coveredIds}; binary failed verification after ${pm} update (${previousVersion ?? "unknown"} \u2192 ${currentVersion ?? "unknown"})` }); logSessionStart(`managed-tool-refresh ${candidate.toolId}: ${pm} update ran but ${binPath} failed verification \u2014 package ${packageName}, covered ids ${coveredIds}; resolution cache cleared for re-probe (${elapsedMs}ms)`); await writeRefreshStamp(candidate.toolId, { checkedAt: now, failed: true, ...currentVersion !== void 0 && { version: currentVersion } }); return { toolId: candidate.toolId, strategy: "npm", packageName: candidate.packageName, previousVersion, currentVersion, changed, verified: false, ok: false, attempted: true }; } await writeRefreshStamp(candidate.toolId, { checkedAt: now, ...currentVersion !== void 0 && { version: currentVersion } }); logSessionStart(changed ? `managed-tool-refresh ${candidate.toolId}: ${previousVersion ?? "unknown"} \u2192 ${currentVersion} via ${pm} update, verified; package ${packageName}, covered ids ${candidate.coveredToolIds?.join(",") ?? candidate.toolId} (${elapsedMs}ms)` : `managed-tool-refresh ${candidate.toolId}: unchanged at ${currentVersion ?? previousVersion ?? "unknown"}; package ${packageName}, covered ids ${candidate.coveredToolIds?.join(",") ?? candidate.toolId} (${elapsedMs}ms)`); return { toolId: candidate.toolId, strategy: "npm", packageName, previousVersion, currentVersion, changed, verified: true, ok: true, attempted: true }; } var refreshInFlight = null; var noopTap = () => { }; function runManagedToolRefresh(now = Date.now()) { const joined = refreshInFlight; if (joined) { logSessionStart("managed-tool-refresh: joining the refresh already in flight"); return joined; } const run = executeManagedToolRefresh(now, noopTap).finally(() => { refreshInFlight = null; }); refreshInFlight = run; return run; } function runManagedToolRefreshForExploration(now, tap) { return executeManagedToolRefresh(now, tap); } async function executeManagedToolRefresh(now, tap = noopTap) { if (process.env.PI_LENS_DISABLE_TOOL_REFRESH === "1") { return { skipped: "disabled", refreshed: [] }; } if (!reserveManagedToolRefreshSlot(maxPerSession())) { return { skipped: "session-budget", refreshed: [] }; } let held = 1; const releaseReservation = () => { while (held > 0) { releaseManagedToolRefreshSlot(); held -= 1; } }; try { const candidates = await installedRefreshCandidates(); await tap(); if (candidates.length === 0) { releaseReservation(); return { skipped: "no-candidates", refreshed: [] }; } const read = await readManagedToolRefreshState(); await tap(); if (read.status === "unreadable") { releaseReservation(); recordDegradationOnce({ kind: "managed-tool-refresh", subject: "refresh-state", reason: `refresh state unreadable (${read.reason}); skipping refresh this session` }); logSessionStart(`managed-tool-refresh: state unreadable (${read.reason}) \u2014 no tool refreshed this session`); return { skipped: "state-unreadable", refreshed: [] }; } const stale = selectStaleTools(candidates, read.state, now); if (stale.length === 0) { releaseReservation(); return { skipped: "nothing-due", refreshed: [] }; } const stalePackages = /* @__PURE__ */ new Set(); const distinctStale = stale.filter((candidate) => { if (candidate.strategy !== "npm" || !candidate.packageName) return true; if (stalePackages.has(candidate.packageName)) return false; stalePackages.add(candidate.packageName); return true; }); const want = Math.min(maxPerSession(), distinctStale.length); while (held < want && reserveManagedToolRefreshSlot(maxPerSession())) { held += 1; } let allowance = held; const refreshed = []; for (const candidate of distinctStale) { if (allowance <= 0) break; allowance -= 1; held -= 1; try { const result = await refreshOne(candidate, read.state.tools[candidate.toolId], now); refreshed.push(result); if (result.attempted && candidate.coveredToolIds) { for (const toolId of candidate.coveredToolIds) { if (toolId === candidate.toolId) continue; await bounded(writeRefreshStamp(toolId, { checkedAt: now, ...result.currentVersion !== void 0 && { version: result.currentVersion }, ...result.ok ? {} : { failed: true } }), { ms: HOOK_WALL_BUDGET_MS.session_start, // The unref'd timer runs after session_start returns, so no // live turn signal belongs to this background refresh. signal: void 0, hook: "session_start", label: "writeRefreshStamp:covered-tool" }); } } await tap(); } catch (err) { recordDegradationOnce({ kind: "managed-tool-refresh", subject: candidate.toolId, reason: `refresh threw: ${err.message}` }); } } return { refreshed }; } finally { releaseReservation(); } } export { getManagedToolRefreshStatePath, readManagedToolRefreshState, runManagedToolRefresh, runManagedToolRefreshForExploration, selectStaleTools, stampManagedToolInstall, stampManagedToolInstalled };