From 6924c722bfcda998f4e4fdb9e5a8f3a2cd884ea7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 07:58:48 +0000 Subject: [PATCH 1/4] fix(pkgman): honor XDG_CACHE_HOME and keep the previous install until the new one is complete Stage the extracted browser next to INSTALL_DIR and swap it in only after download, extraction and version stamping succeed, so a failed fetch no longer removes a working install. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Fu1933SSDGKaJnTEvu4c5b --- README.md | 2 +- src/pkgman.ts | 35 ++++++++++++++++++++++------------- test/pkgman.test.ts | 25 +++++++++++++++++++++---- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0db1274..be86824 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ npm install camoufox-js ``` The Camoufox browser itself is downloaded with `npx camoufox-js fetch` into the -per-user cache directory (`~/.cache/camoufox` on Linux). Set the +per-user cache directory (`$XDG_CACHE_HOME/camoufox` or `~/.cache/camoufox` on Linux). Set the `CAMOUFOX_INSTALL_DIR` environment variable to install and resolve it from a custom location instead — useful in containers and CI images where the home directory is ephemeral or persisted separately (similar to Playwright's diff --git a/src/pkgman.ts b/src/pkgman.ts index 1e0c88e..728fae8 100644 --- a/src/pkgman.ts +++ b/src/pkgman.ts @@ -271,9 +271,12 @@ export class CamoufoxFetcher extends GitHubDownloader { return Buffer.from(await response.arrayBuffer()); } - async extractZip(zipFile: string | Buffer): Promise { + async extractZip( + zipFile: string | Buffer, + destDir: string = INSTALL_DIR.toString(), + ): Promise { const zip = new AdmZip(zipFile); - zip.extractAllTo(INSTALL_DIR.toString(), true); + zip.extractAllTo(destDir, true); } static cleanup(): boolean { @@ -284,37 +287,41 @@ export class CamoufoxFetcher extends GitHubDownloader { return false; } - setVersion(): void { + setVersion(destDir: string = INSTALL_DIR.toString()): void { fs.writeFileSync( - path.join(INSTALL_DIR.toString(), "version.json"), + path.join(destDir, "version.json"), JSON.stringify({ version: this.version, release: this.release }), ); } async install(): Promise { await this.init(); - await CamoufoxFetcher.cleanup(); // Set up outside the try so finally can always tear down the ~600MB staging dir. const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "camoufox-")); const tempFilePath = path.join(tempDir, "camoufox.zip"); const tempFileStream = fs.createWriteStream(tempFilePath); + // Staged next to INSTALL_DIR so the final rename stays on one filesystem. + const installDir = INSTALL_DIR.toString(); + fs.mkdirSync(path.dirname(installDir), { recursive: true }); + const stagingDir = fs.mkdtempSync(`${installDir}.staging-`); try { - fs.mkdirSync(INSTALL_DIR, { recursive: true }); - await webdl(this.url, "Downloading Camoufox...", true, tempFileStream); await new Promise((r) => tempFileStream.close(r)); - await this.extractZip(tempFilePath); - this.setVersion(); + await this.extractZip(tempFilePath, stagingDir); + this.setVersion(stagingDir); if (OS_NAME !== "win") { - execFileSync("chmod", ["-R", "755", INSTALL_DIR.toString()]); + execFileSync("chmod", ["-R", "755", stagingDir]); } + // Replace the previous install only once the new one is complete. + CamoufoxFetcher.cleanup(); + fs.renameSync(stagingDir, installDir); + console.log("Camoufox successfully installed."); } catch (e) { console.error(`Error installing Camoufox: ${e}`); - await CamoufoxFetcher.cleanup(); throw e; } finally { // Best-effort teardown: a throw here would mask the real result, and the caller is fire-and-forget. @@ -326,8 +333,9 @@ export class CamoufoxFetcher extends GitHubDownloader { ); } fs.rmSync(tempDir, { recursive: true, force: true }); + fs.rmSync(stagingDir, { recursive: true, force: true }); } catch (cleanupErr) { - console.error(`Failed to remove staging dir ${tempDir}: ${cleanupErr}`); + console.error(`Failed to remove staging dir: ${cleanupErr}`); } } } @@ -382,7 +390,8 @@ function userCacheDir(appName: string): string { } else if (OS_NAME === "mac") { return path.join(os.homedir(), "Library", "Caches", appName); } else { - return path.join(os.homedir(), ".cache", appName); + const xdg = process.env.XDG_CACHE_HOME?.trim(); + return path.join(xdg || path.join(os.homedir(), ".cache"), appName); } } diff --git a/test/pkgman.test.ts b/test/pkgman.test.ts index 062692c..36286ce 100644 --- a/test/pkgman.test.ts +++ b/test/pkgman.test.ts @@ -19,6 +19,15 @@ describe("INSTALL_DIR", () => { expect(path.isAbsolute(INSTALL_DIR.toString())).toBe(true); }); + test("honors XDG_CACHE_HOME on Linux", async () => { + if (process.platform !== "linux") return; + vi.stubEnv("CAMOUFOX_INSTALL_DIR", ""); + vi.stubEnv("XDG_CACHE_HOME", "/xdg-cache"); + vi.resetModules(); + const { INSTALL_DIR } = await import("../src/pkgman"); + expect(INSTALL_DIR).toBe(path.join("/xdg-cache", "camoufox")); + }); + test("CAMOUFOX_INSTALL_DIR overrides the install location", async () => { const target = path.join("custom", "camoufox-install"); vi.stubEnv("CAMOUFOX_INSTALL_DIR", target); @@ -99,11 +108,12 @@ describe("CamoufoxFetcher.install cleanup", () => { fs.rmSync(tmp, { recursive: true, force: true }); }); - // Staging dirs install() creates: /camoufox-<6 random chars>. + // Staging dirs install() creates: /camoufox-<6 random chars> + // and .staging-<6 random chars>. function stagingDirs(): string[] { return fs .readdirSync(tmp) - .filter((n) => /^camoufox-[A-Za-z0-9]{6}$/.test(n)); + .filter((n) => /^(camoufox-|install\.staging-)[A-Za-z0-9]{6}$/.test(n)); } async function installWith(fetchImpl: ReturnType) { @@ -122,17 +132,24 @@ describe("CamoufoxFetcher.install cleanup", () => { return fetcher; } - test("removes the staging dir when the download fails", async () => { + test("keeps the previous install when the download fails", async () => { + const marker = path.join(installDir, "version.json"); + fs.writeFileSync(marker, "{}"); const fetcher = await installWith(failingFetch()); // The original download error must survive, and no staging dir is left. await expect(fetcher.install()).rejects.toThrow("connection reset"); expect(stagingDirs()).toEqual([]); + expect(fs.existsSync(marker)).toBe(true); }); - test("removes the staging dir after a successful install", async () => { + test("replaces the previous install after a successful install", async () => { + const marker = path.join(installDir, "old-file"); + fs.writeFileSync(marker, ""); const fetcher = await installWith(succeedingFetch()); // A successful install must resolve (not throw) and leave no staging dir. await expect(fetcher.install()).resolves.toBeUndefined(); expect(stagingDirs()).toEqual([]); + expect(fs.existsSync(installDir)).toBe(true); + expect(fs.existsSync(marker)).toBe(false); }); }); From 30896e3ecc7470cdb34751190b66b61ce0f85981 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 08:14:19 +0000 Subject: [PATCH 2/4] fix(pkgman): sweep orphaned staging dirs, swap installs atomically, ignore relative XDG_CACHE_HOME Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Fu1933SSDGKaJnTEvu4c5b --- src/pkgman.ts | 54 +++++++++++++++++++++++++++-------- test/pkgman.test.ts | 68 ++++++++++++++++++++++++++++++++------------- 2 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/pkgman.ts b/src/pkgman.ts index 728fae8..1a6724b 100644 --- a/src/pkgman.ts +++ b/src/pkgman.ts @@ -7,7 +7,7 @@ import type { Writable } from "node:stream"; import { setTimeout } from "node:timers/promises"; import { fileURLToPath } from "node:url"; import AdmZip from "adm-zip"; -import cliProgress, { Options } from "cli-progress"; +import cliProgress, { type Options } from "cli-progress"; import prettyBytes from "pretty-bytes"; import { CONSTRAINTS } from "./__version__.js"; import { @@ -294,16 +294,46 @@ export class CamoufoxFetcher extends GitHubDownloader { ); } + // Remove staging/backup dirs left behind by an interrupted install. + private static removeLeftovers(installDir: string): void { + const parent = path.dirname(installDir); + const base = path.basename(installDir); + for (const name of fs.readdirSync(parent)) { + if ( + name.startsWith(`${base}.staging-`) || + name.startsWith(`${base}.old-`) + ) { + fs.rmSync(path.join(parent, name), { recursive: true, force: true }); + } + } + } + + // Swap the staged install in; restore the previous one if that fails. + private static swapIn(stagingDir: string, installDir: string): void { + const backupDir = fs.existsSync(installDir) + ? `${installDir}.old-${process.pid}` + : undefined; + if (backupDir) fs.renameSync(installDir, backupDir); + try { + fs.renameSync(stagingDir, installDir); + } catch (e) { + if (backupDir) fs.renameSync(backupDir, installDir); + throw e; + } + if (backupDir) fs.rmSync(backupDir, { recursive: true, force: true }); + } + async install(): Promise { await this.init(); - // Set up outside the try so finally can always tear down the ~600MB staging dir. - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "camoufox-")); - const tempFilePath = path.join(tempDir, "camoufox.zip"); - const tempFileStream = fs.createWriteStream(tempFilePath); - // Staged next to INSTALL_DIR so the final rename stays on one filesystem. const installDir = INSTALL_DIR.toString(); fs.mkdirSync(path.dirname(installDir), { recursive: true }); + CamoufoxFetcher.removeLeftovers(installDir); + // Staged next to INSTALL_DIR so the final rename stays on one filesystem. + // Set up outside the try so finally can always tear down the ~600MB staging dirs. const stagingDir = fs.mkdtempSync(`${installDir}.staging-`); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "camoufox-")); + const tempFilePath = path.join(tempDir, "camoufox.zip"); + const tempFileStream = fs.createWriteStream(tempFilePath); try { await webdl(this.url, "Downloading Camoufox...", true, tempFileStream); await new Promise((r) => tempFileStream.close(r)); @@ -315,9 +345,7 @@ export class CamoufoxFetcher extends GitHubDownloader { execFileSync("chmod", ["-R", "755", stagingDir]); } - // Replace the previous install only once the new one is complete. - CamoufoxFetcher.cleanup(); - fs.renameSync(stagingDir, installDir); + CamoufoxFetcher.swapIn(stagingDir, installDir); console.log("Camoufox successfully installed."); } catch (e) { @@ -390,8 +418,12 @@ function userCacheDir(appName: string): string { } else if (OS_NAME === "mac") { return path.join(os.homedir(), "Library", "Caches", appName); } else { - const xdg = process.env.XDG_CACHE_HOME?.trim(); - return path.join(xdg || path.join(os.homedir(), ".cache"), appName); + // Per the XDG spec a relative XDG_CACHE_HOME is invalid and ignored. + const xdg = process.env.XDG_CACHE_HOME; + return path.join( + xdg && path.isAbsolute(xdg) ? xdg : path.join(os.homedir(), ".cache"), + appName, + ); } } diff --git a/test/pkgman.test.ts b/test/pkgman.test.ts index 36286ce..550626d 100644 --- a/test/pkgman.test.ts +++ b/test/pkgman.test.ts @@ -1,6 +1,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; +import AdmZip from "adm-zip"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; // INSTALL_DIR is a module-level constant, so each case re-imports the module @@ -19,14 +20,27 @@ describe("INSTALL_DIR", () => { expect(path.isAbsolute(INSTALL_DIR.toString())).toBe(true); }); - test("honors XDG_CACHE_HOME on Linux", async () => { - if (process.platform !== "linux") return; - vi.stubEnv("CAMOUFOX_INSTALL_DIR", ""); - vi.stubEnv("XDG_CACHE_HOME", "/xdg-cache"); - vi.resetModules(); - const { INSTALL_DIR } = await import("../src/pkgman"); - expect(INSTALL_DIR).toBe(path.join("/xdg-cache", "camoufox")); - }); + test.skipIf(process.platform !== "linux")( + "honors an absolute XDG_CACHE_HOME on Linux", + async () => { + vi.stubEnv("CAMOUFOX_INSTALL_DIR", ""); + vi.stubEnv("XDG_CACHE_HOME", "/xdg-cache"); + vi.resetModules(); + const { INSTALL_DIR } = await import("../src/pkgman"); + expect(INSTALL_DIR).toBe(path.join("/xdg-cache", "camoufox")); + }, + ); + + test.skipIf(process.platform !== "linux")( + "ignores a relative XDG_CACHE_HOME", + async () => { + vi.stubEnv("CAMOUFOX_INSTALL_DIR", ""); + vi.stubEnv("XDG_CACHE_HOME", "relative/cache"); + vi.resetModules(); + const { INSTALL_DIR } = await import("../src/pkgman"); + expect(INSTALL_DIR).toBe(path.join(os.homedir(), ".cache", "camoufox")); + }, + ); test("CAMOUFOX_INSTALL_DIR overrides the install location", async () => { const target = path.join("custom", "camoufox-install"); @@ -72,13 +86,16 @@ function failingFetch() { })); } -// A fetch that writes a few bytes and completes cleanly. +// A fetch that serves a small real zip and completes cleanly. function succeedingFetch() { + const zip = new AdmZip(); + zip.addFile("camoufox", Buffer.from("binary")); + const bytes = new Uint8Array(zip.toBuffer()); return vi.fn(async () => ({ ok: true, headers: { get: () => "0" }, body: (async function* () { - yield new Uint8Array([1, 2, 3]); + yield bytes; })(), })); } @@ -108,25 +125,25 @@ describe("CamoufoxFetcher.install cleanup", () => { fs.rmSync(tmp, { recursive: true, force: true }); }); - // Staging dirs install() creates: /camoufox-<6 random chars> - // and .staging-<6 random chars>. + // Dirs install() creates: /camoufox-<6 random chars>, + // .staging-<6 random chars> and .old-. function stagingDirs(): string[] { return fs .readdirSync(tmp) - .filter((n) => /^(camoufox-|install\.staging-)[A-Za-z0-9]{6}$/.test(n)); + .filter((n) => + /^(camoufox-[A-Za-z0-9]{6}|install\.(staging|old)-.+)$/.test(n), + ); } async function installWith(fetchImpl: ReturnType) { const { CamoufoxFetcher } = await import("../src/pkgman"); const fetcher = new CamoufoxFetcher(); - // Skip the release lookup; just hand install() a URL to download. + // Skip the release lookup; just hand install() a URL and version. vi.spyOn(fetcher, "init").mockImplementation(async () => { - (fetcher as unknown as { _url: string })._url = - "https://example.test/camoufox.zip"; + const f = fetcher as unknown as { _url: string; _version_obj: unknown }; + f._url = "https://example.test/camoufox.zip"; + f._version_obj = { version: "1.0", release: "beta.1" }; }); - // Don't extract the placeholder zip or write a version file. - vi.spyOn(fetcher, "extractZip").mockResolvedValue(undefined); - vi.spyOn(fetcher, "setVersion").mockImplementation(() => {}); vi.stubGlobal("fetch", fetchImpl); vi.stubGlobal("console", { ...console, error: vi.fn(), log: vi.fn() }); return fetcher; @@ -145,11 +162,22 @@ describe("CamoufoxFetcher.install cleanup", () => { test("replaces the previous install after a successful install", async () => { const marker = path.join(installDir, "old-file"); fs.writeFileSync(marker, ""); + // Leftovers from an interrupted earlier install must be swept. + fs.mkdirSync(path.join(tmp, "install.staging-abc123")); + fs.mkdirSync(path.join(tmp, "install.old-1")); const fetcher = await installWith(succeedingFetch()); // A successful install must resolve (not throw) and leave no staging dir. await expect(fetcher.install()).resolves.toBeUndefined(); expect(stagingDirs()).toEqual([]); - expect(fs.existsSync(installDir)).toBe(true); expect(fs.existsSync(marker)).toBe(false); + expect(fs.readdirSync(installDir).sort()).toEqual([ + "camoufox", + "version.json", + ]); + expect( + JSON.parse( + fs.readFileSync(path.join(installDir, "version.json"), "utf8"), + ), + ).toEqual({ version: "1.0", release: "beta.1" }); }); }); From 03d02f53f38d421c6246d927bfbe1970b224c42c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 09:38:49 +0000 Subject: [PATCH 3/4] fix(pkgman): restore an orphaned backup and swap the target of a symlinked install dir Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Fu1933SSDGKaJnTEvu4c5b --- src/pkgman.ts | 13 ++++++++++--- test/pkgman.test.ts | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/pkgman.ts b/src/pkgman.ts index 1a6724b..08fd04b 100644 --- a/src/pkgman.ts +++ b/src/pkgman.ts @@ -295,15 +295,19 @@ export class CamoufoxFetcher extends GitHubDownloader { } // Remove staging/backup dirs left behind by an interrupted install. + // A backup whose install dir is missing is the only valid copy: restore it. private static removeLeftovers(installDir: string): void { const parent = path.dirname(installDir); const base = path.basename(installDir); for (const name of fs.readdirSync(parent)) { - if ( + const full = path.join(parent, name); + if (name.startsWith(`${base}.old-`) && !fs.existsSync(installDir)) { + fs.renameSync(full, installDir); + } else if ( name.startsWith(`${base}.staging-`) || name.startsWith(`${base}.old-`) ) { - fs.rmSync(path.join(parent, name), { recursive: true, force: true }); + fs.rmSync(full, { recursive: true, force: true }); } } } @@ -325,7 +329,10 @@ export class CamoufoxFetcher extends GitHubDownloader { async install(): Promise { await this.init(); - const installDir = INSTALL_DIR.toString(); + // Resolve a symlinked install dir so the swap replaces its target, not the link. + const installDir = fs.existsSync(INSTALL_DIR) + ? fs.realpathSync(INSTALL_DIR) + : INSTALL_DIR.toString(); fs.mkdirSync(path.dirname(installDir), { recursive: true }); CamoufoxFetcher.removeLeftovers(installDir); // Staged next to INSTALL_DIR so the final rename stays on one filesystem. diff --git a/test/pkgman.test.ts b/test/pkgman.test.ts index 550626d..ba9c267 100644 --- a/test/pkgman.test.ts +++ b/test/pkgman.test.ts @@ -131,7 +131,9 @@ describe("CamoufoxFetcher.install cleanup", () => { return fs .readdirSync(tmp) .filter((n) => - /^(camoufox-[A-Za-z0-9]{6}|install\.(staging|old)-.+)$/.test(n), + /^(camoufox-[A-Za-z0-9]{6}|(install|target)\.(staging|old)-.+)$/.test( + n, + ), ); } @@ -159,6 +161,35 @@ describe("CamoufoxFetcher.install cleanup", () => { expect(fs.existsSync(marker)).toBe(true); }); + test("restores a backup left behind when the install dir is missing", async () => { + fs.rmSync(installDir, { recursive: true }); + fs.mkdirSync(path.join(tmp, "install.old-1")); + fs.writeFileSync(path.join(tmp, "install.old-1", "version.json"), "{}"); + const fetcher = await installWith(failingFetch()); + await expect(fetcher.install()).rejects.toThrow("connection reset"); + expect(stagingDirs()).toEqual([]); + expect(fs.existsSync(path.join(installDir, "version.json"))).toBe(true); + }); + + test.skipIf(process.platform === "win32")( + "swaps the target of a symlinked install dir and keeps the link", + async () => { + const target = path.join(tmp, "target"); + fs.mkdirSync(target); + fs.writeFileSync(path.join(target, "old-file"), ""); + fs.rmSync(installDir, { recursive: true }); + fs.symlinkSync(target, installDir); + const fetcher = await installWith(succeedingFetch()); + await expect(fetcher.install()).resolves.toBeUndefined(); + expect(stagingDirs()).toEqual([]); + expect(fs.lstatSync(installDir).isSymbolicLink()).toBe(true); + expect(fs.readdirSync(target).sort()).toEqual([ + "camoufox", + "version.json", + ]); + }, + ); + test("replaces the previous install after a successful install", async () => { const marker = path.join(installDir, "old-file"); fs.writeFileSync(marker, ""); From 620b21fafc15fe481c4eb5ce602429db8a6747f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 09:47:03 +0000 Subject: [PATCH 4/4] refactor(pkgman): replace the previous install with delete-then-rename Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Fu1933SSDGKaJnTEvu4c5b --- src/pkgman.ts | 36 ++++++++---------------------------- test/pkgman.test.ts | 19 +++---------------- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/src/pkgman.ts b/src/pkgman.ts index 08fd04b..8fdcedf 100644 --- a/src/pkgman.ts +++ b/src/pkgman.ts @@ -294,39 +294,17 @@ export class CamoufoxFetcher extends GitHubDownloader { ); } - // Remove staging/backup dirs left behind by an interrupted install. - // A backup whose install dir is missing is the only valid copy: restore it. + // Remove staging dirs left behind by an interrupted install. private static removeLeftovers(installDir: string): void { const parent = path.dirname(installDir); - const base = path.basename(installDir); + const prefix = `${path.basename(installDir)}.staging-`; for (const name of fs.readdirSync(parent)) { - const full = path.join(parent, name); - if (name.startsWith(`${base}.old-`) && !fs.existsSync(installDir)) { - fs.renameSync(full, installDir); - } else if ( - name.startsWith(`${base}.staging-`) || - name.startsWith(`${base}.old-`) - ) { - fs.rmSync(full, { recursive: true, force: true }); + if (name.startsWith(prefix)) { + fs.rmSync(path.join(parent, name), { recursive: true, force: true }); } } } - // Swap the staged install in; restore the previous one if that fails. - private static swapIn(stagingDir: string, installDir: string): void { - const backupDir = fs.existsSync(installDir) - ? `${installDir}.old-${process.pid}` - : undefined; - if (backupDir) fs.renameSync(installDir, backupDir); - try { - fs.renameSync(stagingDir, installDir); - } catch (e) { - if (backupDir) fs.renameSync(backupDir, installDir); - throw e; - } - if (backupDir) fs.rmSync(backupDir, { recursive: true, force: true }); - } - async install(): Promise { await this.init(); // Resolve a symlinked install dir so the swap replaces its target, not the link. @@ -335,7 +313,7 @@ export class CamoufoxFetcher extends GitHubDownloader { : INSTALL_DIR.toString(); fs.mkdirSync(path.dirname(installDir), { recursive: true }); CamoufoxFetcher.removeLeftovers(installDir); - // Staged next to INSTALL_DIR so the final rename stays on one filesystem. + // Staged next to the install dir so the final rename stays on one filesystem. // Set up outside the try so finally can always tear down the ~600MB staging dirs. const stagingDir = fs.mkdtempSync(`${installDir}.staging-`); const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "camoufox-")); @@ -352,7 +330,9 @@ export class CamoufoxFetcher extends GitHubDownloader { execFileSync("chmod", ["-R", "755", stagingDir]); } - CamoufoxFetcher.swapIn(stagingDir, installDir); + // Replace the previous install only once the new one is complete. + fs.rmSync(installDir, { recursive: true, force: true }); + fs.renameSync(stagingDir, installDir); console.log("Camoufox successfully installed."); } catch (e) { diff --git a/test/pkgman.test.ts b/test/pkgman.test.ts index ba9c267..46376f3 100644 --- a/test/pkgman.test.ts +++ b/test/pkgman.test.ts @@ -125,15 +125,13 @@ describe("CamoufoxFetcher.install cleanup", () => { fs.rmSync(tmp, { recursive: true, force: true }); }); - // Dirs install() creates: /camoufox-<6 random chars>, - // .staging-<6 random chars> and .old-. + // Dirs install() creates: /camoufox-<6 random chars> + // and .staging-<6 random chars>. function stagingDirs(): string[] { return fs .readdirSync(tmp) .filter((n) => - /^(camoufox-[A-Za-z0-9]{6}|(install|target)\.(staging|old)-.+)$/.test( - n, - ), + /^(camoufox-[A-Za-z0-9]{6}|(install|target)\.staging-.+)$/.test(n), ); } @@ -161,16 +159,6 @@ describe("CamoufoxFetcher.install cleanup", () => { expect(fs.existsSync(marker)).toBe(true); }); - test("restores a backup left behind when the install dir is missing", async () => { - fs.rmSync(installDir, { recursive: true }); - fs.mkdirSync(path.join(tmp, "install.old-1")); - fs.writeFileSync(path.join(tmp, "install.old-1", "version.json"), "{}"); - const fetcher = await installWith(failingFetch()); - await expect(fetcher.install()).rejects.toThrow("connection reset"); - expect(stagingDirs()).toEqual([]); - expect(fs.existsSync(path.join(installDir, "version.json"))).toBe(true); - }); - test.skipIf(process.platform === "win32")( "swaps the target of a symlinked install dir and keeps the link", async () => { @@ -195,7 +183,6 @@ describe("CamoufoxFetcher.install cleanup", () => { fs.writeFileSync(marker, ""); // Leftovers from an interrupted earlier install must be swept. fs.mkdirSync(path.join(tmp, "install.staging-abc123")); - fs.mkdirSync(path.join(tmp, "install.old-1")); const fetcher = await installWith(succeedingFetch()); // A successful install must resolve (not throw) and leave no staging dir. await expect(fetcher.install()).resolves.toBeUndefined();