mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
fix(cli): serialize managed uninstall
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
535b1b7cd9
commit
72f8f84884
3 files changed
+50
-9
No files matched your search
@@ -4,7 +4,16 @@ import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { installCommand, resolveNpmInstallRequest } from "../commands/install.js";
|
||||
import { uninstallCommand } from "../commands/uninstall.js";
|
||||
import { readInstallManifest, resolveInstallStorePaths } from "../install-store.js";
|
||||
import {
|
||||
INSTALL_MANIFEST_VERSION,
|
||||
flipCurrentAtomic,
|
||||
initializeInstallStore,
|
||||
payloadPathFor,
|
||||
readInstallManifest,
|
||||
resolveInstallStorePaths,
|
||||
withInstallStoreLock,
|
||||
writeInstallManifestAtomic,
|
||||
} from "../install-store.js";
|
||||
import { resolveCliVersion } from "../version.js";
|
||||
|
||||
const ORIGINAL_ENV = { ...process.env };
|
||||
@@ -120,4 +129,29 @@ describe("managed install commands", () => {
|
||||
await expect(uninstallCommand()).rejects.toThrow("unverified install store");
|
||||
expect(fs.readFileSync(unrelatedFile, "utf8")).toBe("keep");
|
||||
});
|
||||
|
||||
it("refuses to uninstall while another store mutation holds the lock", async () => {
|
||||
const paths = resolveInstallStorePaths();
|
||||
const payloadPath = payloadPathFor(paths, "npm", "2026.720.0");
|
||||
initializeInstallStore(paths);
|
||||
fs.mkdirSync(payloadPath, { recursive: true });
|
||||
flipCurrentAtomic(payloadPath, paths);
|
||||
writeInstallManifestAtomic({
|
||||
schemaVersion: INSTALL_MANIFEST_VERSION,
|
||||
source: "npm",
|
||||
version: "2026.720.0",
|
||||
channel: "latest",
|
||||
payloadPath,
|
||||
installedAt: "2026-07-22T18:00:00.000Z",
|
||||
previous: [],
|
||||
}, paths);
|
||||
|
||||
await withInstallStoreLock(
|
||||
async () => {
|
||||
await expect(uninstallCommand()).rejects.toThrow("already running");
|
||||
},
|
||||
paths,
|
||||
);
|
||||
expect(fs.existsSync(paths.lockPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
removeManagedPathBlock,
|
||||
removeManagedShim,
|
||||
resolveInstallStorePaths,
|
||||
withInstallStoreLock,
|
||||
} from "../install-store.js";
|
||||
import { resolvePaperclipInstanceId } from "../config/home.js";
|
||||
import { detectServiceManager } from "../services/service-manager.js";
|
||||
@@ -25,14 +26,19 @@ export async function uninstallCommand(
|
||||
}
|
||||
|
||||
const paths = resolveInstallStorePaths();
|
||||
if (fs.existsSync(paths.cliRoot)) assertManagedInstallStore(paths);
|
||||
const shimRemoved = removeManagedShim(paths);
|
||||
fs.rmSync(paths.cliRoot, { recursive: true, force: true });
|
||||
const hadStore = fs.existsSync(paths.cliRoot);
|
||||
if (hadStore) assertManagedInstallStore(paths);
|
||||
const shimRemoved = await withInstallStoreLock(async () => {
|
||||
if (hadStore) assertManagedInstallStore(paths);
|
||||
const removed = removeManagedShim(paths);
|
||||
|
||||
const home = process.env.HOME;
|
||||
for (const rcFile of home ? [path.join(home, ".bashrc"), path.join(home, ".zshrc")] : []) {
|
||||
removeManagedPathBlock(rcFile);
|
||||
}
|
||||
const home = process.env.HOME;
|
||||
for (const rcFile of home ? [path.join(home, ".bashrc"), path.join(home, ".zshrc")] : []) {
|
||||
removeManagedPathBlock(rcFile);
|
||||
}
|
||||
fs.rmSync(paths.cliRoot, { recursive: true, force: true });
|
||||
return removed;
|
||||
}, paths, { initialize: !hadStore });
|
||||
|
||||
if (!shimRemoved) {
|
||||
console.log(pc.yellow(`Left ${paths.shimPath} unchanged because it is not a Paperclip-managed shim.`));
|
||||
|
||||
@@ -156,8 +156,9 @@ function processIsAlive(pid: number): boolean {
|
||||
export async function withInstallStoreLock<T>(
|
||||
callback: () => Promise<T>,
|
||||
paths = resolveInstallStorePaths(),
|
||||
options: { initialize?: boolean } = {},
|
||||
): Promise<T> {
|
||||
initializeInstallStore(paths);
|
||||
if (options.initialize !== false) initializeInstallStore(paths);
|
||||
const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
|
||||
const acquire = (): void => {
|
||||
const temporaryPath = `${paths.lockPath}.${token}.tmp`;
|
||||
|
||||
Reference in new issue
Block a user