Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions apps/code/src/main/services/posthog-plugin/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ vi.mock("node:fs/promises", async () => {
return { ...fs.promises, default: fs.promises };
});

vi.mock("../../utils/extract-zip.js", () => ({
extractZip: mockExtractZip,
}));

const mockFflateUnzipSync = vi.hoisted(() => vi.fn());
const mockFflateUnzip = vi.hoisted(() => vi.fn());
vi.mock("fflate", () => ({
unzipSync: mockFflateUnzipSync,
unzip: mockFflateUnzip,
}));

vi.mock("../../utils/extract-zip.js", async () => {
const actual = await vi.importActual<
typeof import("../../utils/extract-zip.js")
>("../../utils/extract-zip.js");
return {
...actual,
extractZip: mockExtractZip,
};
});

vi.mock("node:os", () => ({
homedir: () => "/mock/home",
tmpdir: () => "/mock/tmp",
Expand Down Expand Up @@ -97,7 +103,7 @@ function simulateExtractZip() {
mockExtractZip.mockImplementation(
async (zipPath: string, extractDir: string) => {
if (zipPath.includes("context-mill")) {
// Context-mill outer zip: produce omnibus-*.zip files (dummy bytes — unzipSync is mocked)
// Inner zip bytes are dummy — fflate.unzip is mocked below.
vol.mkdirSync(extractDir, { recursive: true });
vol.writeFileSync(`${extractDir}/omnibus-test-skill.zip`, "dummy");
vol.writeFileSync(`${extractDir}/manifest.json`, "{}");
Expand All @@ -116,12 +122,18 @@ function simulateExtractZip() {
},
);

// Mock fflate unzipSync for inner zip extraction
mockFflateUnzipSync.mockImplementation(() => ({
"SKILL.md": new TextEncoder().encode(
"---\nname: omnibus-test-skill\n---\n# Test Skill",
),
}));
mockFflateUnzip.mockImplementation(
(
_data: Uint8Array,
cb: (err: Error | null, data: Record<string, Uint8Array>) => void,
) => {
cb(null, {
"SKILL.md": new TextEncoder().encode(
"---\nname: omnibus-test-skill\n---\n# Test Skill",
),
});
},
);
}

/** Create the bundled plugin directory in memfs */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import {
writeFile,
} from "node:fs/promises";
import { basename, dirname, join } from "node:path";
import { extractZip } from "@main/utils/extract-zip";
import { extractZip, unzipAsync } from "@main/utils/extract-zip";
import { Saga } from "@posthog/shared";
import { unzipSync } from "fflate";

/**
* Overlays previously-downloaded skills on top of the runtime plugin dir.
Expand Down Expand Up @@ -292,7 +291,7 @@ export class UpdateSkillsSaga extends Saga<
const strippedName = file.replace(/^omnibus-/, "").replace(/\.zip$/, "");
const innerZipPath = join(extractDir, file);
const innerZipData = await readFile(innerZipPath);
const innerEntries = unzipSync(new Uint8Array(innerZipData));
const innerEntries = await unzipAsync(new Uint8Array(innerZipData));
const skillDestDir = join(destDir, strippedName);
await mkdir(skillDestDir, { recursive: true });

Expand Down
15 changes: 13 additions & 2 deletions apps/code/src/main/utils/extract-zip.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { unzipSync } from "fflate";
import { type Unzipped, unzip } from "fflate";

// fflate's async unzip yields the event loop so the Electron main thread
// stays responsive on large archives. Do not switch back to unzipSync.
export function unzipAsync(data: Uint8Array): Promise<Unzipped> {
return new Promise((resolve, reject) => {
unzip(data, (err, unzipped) => {
if (err) reject(err);
else resolve(unzipped);
});
});
}

/**
* Extracts a ZIP file to a directory using fflate (cross-platform, no native dependencies).
Expand All @@ -10,7 +21,7 @@ export async function extractZip(
extractDir: string,
): Promise<void> {
const data = await readFile(zipPath);
const unzipped = unzipSync(new Uint8Array(data));
const unzipped = await unzipAsync(new Uint8Array(data));
for (const [filename, content] of Object.entries(unzipped)) {
const fullPath = join(extractDir, filename);
if (filename.endsWith("/")) {
Expand Down
Loading