@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Windows npm self-updates to move loaded native dependency packages out of the active install before reinstalling pi ([#4157](https://github.com/earendil-works/pi/issues/4157)).
|
||||||
|
|
||||||
## [0.75.1] - 2026-05-18
|
## [0.75.1] - 2026-05-18
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { processFileArguments } from "./cli/file-processor.js";
|
|||||||
import { buildInitialMessage } from "./cli/initial-message.js";
|
import { buildInitialMessage } from "./cli/initial-message.js";
|
||||||
import { listModels } from "./cli/list-models.js";
|
import { listModels } from "./cli/list-models.js";
|
||||||
import { selectSession } from "./cli/session-picker.js";
|
import { selectSession } from "./cli/session-picker.js";
|
||||||
import { ENV_SESSION_DIR, expandTildePath, getAgentDir, VERSION } from "./config.js";
|
import { ENV_SESSION_DIR, expandTildePath, getAgentDir, getPackageDir, VERSION } from "./config.js";
|
||||||
import { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from "./core/agent-session-runtime.js";
|
import { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from "./core/agent-session-runtime.js";
|
||||||
import {
|
import {
|
||||||
type AgentSessionRuntimeDiagnostic,
|
type AgentSessionRuntimeDiagnostic,
|
||||||
@@ -46,6 +46,7 @@ import { ExtensionSelectorComponent } from "./modes/interactive/components/exten
|
|||||||
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
||||||
import { handleConfigCommand, handlePackageCommand } from "./package-manager-cli.js";
|
import { handleConfigCommand, handlePackageCommand } from "./package-manager-cli.js";
|
||||||
import { isLocalPath } from "./utils/paths.js";
|
import { isLocalPath } from "./utils/paths.js";
|
||||||
|
import { cleanupWindowsSelfUpdateQuarantine } from "./utils/windows-self-update.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read all content from piped stdin.
|
* Read all content from piped stdin.
|
||||||
@@ -428,6 +429,10 @@ export async function main(args: string[], options?: MainOptions) {
|
|||||||
process.env.PI_SKIP_VERSION_CHECK = "1";
|
process.env.PI_SKIP_VERSION_CHECK = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
cleanupWindowsSelfUpdateQuarantine(getPackageDir());
|
||||||
|
}
|
||||||
|
|
||||||
if (await handlePackageCommand(args)) {
|
if (await handlePackageCommand(args)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { spawn } from "child_process";
|
|||||||
import { selectConfig } from "./cli/config-selector.js";
|
import { selectConfig } from "./cli/config-selector.js";
|
||||||
import {
|
import {
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
|
detectInstallMethod,
|
||||||
getAgentDir,
|
getAgentDir,
|
||||||
|
getPackageDir,
|
||||||
getSelfUpdateCommand,
|
getSelfUpdateCommand,
|
||||||
getSelfUpdateUnavailableInstruction,
|
getSelfUpdateUnavailableInstruction,
|
||||||
PACKAGE_NAME,
|
PACKAGE_NAME,
|
||||||
@@ -14,6 +16,10 @@ import { DefaultPackageManager } from "./core/package-manager.js";
|
|||||||
import { SettingsManager } from "./core/settings-manager.js";
|
import { SettingsManager } from "./core/settings-manager.js";
|
||||||
import { resolveSpawnCommand } from "./utils/child-process.js";
|
import { resolveSpawnCommand } from "./utils/child-process.js";
|
||||||
import { getLatestPiRelease, isNewerPackageVersion } from "./utils/version-check.js";
|
import { getLatestPiRelease, isNewerPackageVersion } from "./utils/version-check.js";
|
||||||
|
import {
|
||||||
|
cleanupWindowsSelfUpdateQuarantine,
|
||||||
|
quarantineWindowsNativeDependencies,
|
||||||
|
} from "./utils/windows-self-update.js";
|
||||||
|
|
||||||
export type PackageCommand = "install" | "remove" | "update" | "list";
|
export type PackageCommand = "install" | "remove" | "update" | "list";
|
||||||
|
|
||||||
@@ -336,6 +342,16 @@ async function runSelfUpdate(command: SelfUpdateCommand): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareWindowsNpmSelfUpdate(): void {
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageDir = getPackageDir();
|
||||||
|
cleanupWindowsSelfUpdateQuarantine(packageDir);
|
||||||
|
quarantineWindowsNativeDependencies(packageDir);
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleConfigCommand(args: string[]): Promise<boolean> {
|
export async function handleConfigCommand(args: string[]): Promise<boolean> {
|
||||||
if (args[0] !== "config") {
|
if (args[0] !== "config") {
|
||||||
return false;
|
return false;
|
||||||
@@ -489,6 +505,13 @@ export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
|||||||
if (!selfUpdatePlan.shouldRun) {
|
if (!selfUpdatePlan.shouldRun) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
const installMethod = detectInstallMethod();
|
||||||
|
if (process.platform === "win32" && installMethod !== "npm") {
|
||||||
|
console.error(chalk.red(`${APP_NAME} self-update on Windows is only supported for npm installs.`));
|
||||||
|
console.error(chalk.dim(`Detected install method: ${installMethod}. Update ${APP_NAME} manually.`));
|
||||||
|
process.exitCode = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const selfUpdateCommand = getSelfUpdateCommand(
|
const selfUpdateCommand = getSelfUpdateCommand(
|
||||||
PACKAGE_NAME,
|
PACKAGE_NAME,
|
||||||
selfUpdateNpmCommand,
|
selfUpdateNpmCommand,
|
||||||
@@ -500,6 +523,7 @@ export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
prepareWindowsNpmSelfUpdate();
|
||||||
await runSelfUpdate(selfUpdateCommand);
|
await runSelfUpdate(selfUpdateCommand);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = error instanceof Error ? error.message : "Unknown package command error";
|
const message = error instanceof Error ? error.message : "Unknown package command error";
|
||||||
|
|||||||
84
packages/coding-agent/src/utils/windows-self-update.ts
Normal file
84
packages/coding-agent/src/utils/windows-self-update.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { copyFileSync, existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
||||||
|
import { basename, dirname, join, relative, resolve, toNamespacedPath } from "node:path";
|
||||||
|
import { getCwdRelativePath } from "./paths.js";
|
||||||
|
|
||||||
|
const QUARANTINE_DIR_NAME = ".pi-native-quarantine";
|
||||||
|
|
||||||
|
function normalizePath(path: string): string {
|
||||||
|
return toNamespacedPath(resolve(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQuarantineRoot(packageDir: string): string | undefined {
|
||||||
|
let current = resolve(packageDir);
|
||||||
|
while (true) {
|
||||||
|
if (basename(current).toLowerCase() === "node_modules") {
|
||||||
|
return join(current, QUARANTINE_DIR_NAME);
|
||||||
|
}
|
||||||
|
const parent = dirname(current);
|
||||||
|
if (parent === current) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoadedSharedObjectsInPackageDir(packageDir: string): string[] {
|
||||||
|
const sharedObjects = (process.report.getReport() as { sharedObjects?: unknown }).sharedObjects;
|
||||||
|
if (!Array.isArray(sharedObjects)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = normalizePath(packageDir).toLowerCase();
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const loadedFiles: string[] = [];
|
||||||
|
for (const value of sharedObjects) {
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const filePath = normalizePath(value);
|
||||||
|
const comparisonPath = filePath.toLowerCase();
|
||||||
|
if (getCwdRelativePath(comparisonPath, root) === undefined || seen.has(comparisonPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(comparisonPath);
|
||||||
|
loadedFiles.push(filePath);
|
||||||
|
}
|
||||||
|
return loadedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanupWindowsSelfUpdateQuarantine(packageDir: string): void {
|
||||||
|
const quarantineRoot = getQuarantineRoot(packageDir);
|
||||||
|
if (!quarantineRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
rmSync(quarantineRoot, { recursive: true, force: true });
|
||||||
|
} catch {
|
||||||
|
// A previous pi process may still be exiting and holding a native addon.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function quarantineWindowsNativeDependencies(packageDir: string): void {
|
||||||
|
const resolvedPackageDir = normalizePath(packageDir);
|
||||||
|
const quarantineRoot = getQuarantineRoot(resolvedPackageDir);
|
||||||
|
if (!quarantineRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedFiles = getLoadedSharedObjectsInPackageDir(resolvedPackageDir);
|
||||||
|
if (loadedFiles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarantineRunDir = join(quarantineRoot, `${Date.now()}-${process.pid}-${randomUUID()}`);
|
||||||
|
for (const loadedFile of loadedFiles) {
|
||||||
|
if (!existsSync(loadedFile)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const quarantinePath = join(quarantineRunDir, relative(resolvedPackageDir, loadedFile));
|
||||||
|
mkdirSync(dirname(quarantinePath), { recursive: true });
|
||||||
|
renameSync(loadedFile, quarantinePath);
|
||||||
|
copyFileSync(quarantinePath, loadedFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user