FN-5988: fix desktop startup by lazy-loading archive parser deps
Prevent desktop startup failures by deferring archive parser dependencies until archive parsing runs. - lazy-load `extract-zip` and `tar` inside archive parsing paths instead of at module import time - add regression tests covering helper-only imports and dynamic loading for zip/tgz archive parsing - include the desktop package's required @fusion/core runtime dependency groups in electron-builder packaging Files changed: .../src/__tests__/agent-companies-parser.test.ts | 115 +++++++++++++++++++++ packages/core/src/agent-companies-parser.ts | 4 +- packages/desktop/electron-builder.yml | 44 ++++++++ 3 files changed, 161 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-5988 Fusion-Task-Lineage: b31bf43b-791c-4cd1-a62f-7095badcb706
This commit is contained in:
@@ -659,6 +659,121 @@ name: Nested Archive CEO
|
||||
});
|
||||
});
|
||||
|
||||
describe("lazy-load isolation", () => {
|
||||
it("does not load extract-zip or tar for pure manifest helpers", async () => {
|
||||
const extractZipModuleFactory = vi.fn(() => ({
|
||||
default: vi.fn(async () => undefined),
|
||||
}));
|
||||
const tarModuleFactory = vi.fn(() => ({
|
||||
x: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.resetModules();
|
||||
vi.doMock("extract-zip", extractZipModuleFactory);
|
||||
vi.doMock("tar", tarModuleFactory);
|
||||
|
||||
const parserModule = await import("../agent-companies-parser.js");
|
||||
|
||||
const parsed = parserModule.parseYamlFrontmatter(`---
|
||||
name: Pure Helper
|
||||
---
|
||||
Body`);
|
||||
expect(parsed.frontmatter.name).toBe("Pure Helper");
|
||||
expect(parsed.body).toBe("Body");
|
||||
|
||||
const manifest = parserModule.parseAgentManifest(`---
|
||||
name: Pure Agent
|
||||
skills:
|
||||
- review
|
||||
---
|
||||
Keep things tidy.`);
|
||||
expect(manifest.name).toBe("Pure Agent");
|
||||
|
||||
const prepared = parserModule.prepareAgentCompaniesImport({
|
||||
company: { name: "Pure Company" },
|
||||
agents: [{ name: "Pure Agent", title: "Reviewer", instructionBody: "Keep things tidy." }],
|
||||
teams: [],
|
||||
projects: [],
|
||||
tasks: [],
|
||||
});
|
||||
expect(prepared.items).toHaveLength(1);
|
||||
|
||||
const converted = parserModule.convertAgentCompanies({
|
||||
company: { name: "Pure Company" },
|
||||
agents: [{ name: "Pure Agent", title: "Reviewer", instructionBody: "Keep things tidy." }],
|
||||
teams: [],
|
||||
projects: [],
|
||||
tasks: [],
|
||||
skills: [],
|
||||
});
|
||||
expect(converted.inputs).toHaveLength(1);
|
||||
|
||||
expect(extractZipModuleFactory).not.toHaveBeenCalled();
|
||||
expect(tarModuleFactory).not.toHaveBeenCalled();
|
||||
|
||||
vi.doUnmock("extract-zip");
|
||||
vi.doUnmock("tar");
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("dynamically imports extract-zip when parsing zip archives", async () => {
|
||||
const extractZip = vi.fn(async (_archivePath: string, options: { dir: string }) => {
|
||||
writeTextFile(join(options.dir, "zip-company", "COMPANY.md"), `---\nname: Mock Zip Company\n---`);
|
||||
writeTextFile(join(options.dir, "zip-company", "agents", "ceo", "AGENTS.md"), `---\nname: Mock Zip CEO\n---`);
|
||||
});
|
||||
const extractZipModuleFactory = vi.fn(() => ({ default: extractZip }));
|
||||
|
||||
vi.resetModules();
|
||||
vi.doMock("extract-zip", extractZipModuleFactory);
|
||||
|
||||
const parserModule = await import("../agent-companies-parser.js");
|
||||
const root = createTempDir();
|
||||
const archivePath = join(root, "company.zip");
|
||||
writeFileSync(archivePath, Buffer.from("placeholder zip contents"));
|
||||
|
||||
const pkg = await parserModule.parseCompanyArchive(archivePath);
|
||||
expect(pkg.company?.name).toBe("Mock Zip Company");
|
||||
expect(pkg.agents[0]?.name).toBe("Mock Zip CEO");
|
||||
expect(extractZipModuleFactory).toHaveBeenCalledTimes(1);
|
||||
expect(extractZip).toHaveBeenCalledTimes(1);
|
||||
expect(extractZip).toHaveBeenCalledWith(
|
||||
archivePath,
|
||||
expect.objectContaining({ dir: expect.any(String) }),
|
||||
);
|
||||
|
||||
vi.doUnmock("extract-zip");
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("dynamically imports tar when parsing tgz archives", async () => {
|
||||
const tarExtract = vi.fn(async (options: { file: string; cwd: string }) => {
|
||||
writeTextFile(join(options.cwd, "tar-company", "COMPANY.md"), `---\nname: Mock Tar Company\n---`);
|
||||
writeTextFile(join(options.cwd, "tar-company", "agents", "ceo", "AGENTS.md"), `---\nname: Mock Tar CEO\n---`);
|
||||
});
|
||||
const tarModuleFactory = vi.fn(() => ({ x: tarExtract }));
|
||||
|
||||
vi.resetModules();
|
||||
vi.doMock("tar", tarModuleFactory);
|
||||
|
||||
const parserModule = await import("../agent-companies-parser.js");
|
||||
const root = createTempDir();
|
||||
const archivePath = join(root, "company.tgz");
|
||||
writeFileSync(archivePath, Buffer.from("placeholder tgz contents"));
|
||||
|
||||
const pkg = await parserModule.parseCompanyArchive(archivePath);
|
||||
expect(pkg.company?.name).toBe("Mock Tar Company");
|
||||
expect(pkg.agents[0]?.name).toBe("Mock Tar CEO");
|
||||
expect(tarModuleFactory).toHaveBeenCalledTimes(1);
|
||||
expect(tarExtract).toHaveBeenCalledTimes(1);
|
||||
expect(tarExtract).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ file: archivePath, cwd: expect.any(String) }),
|
||||
);
|
||||
|
||||
vi.doUnmock("tar");
|
||||
vi.resetModules();
|
||||
});
|
||||
});
|
||||
|
||||
describe("conversion", () => {
|
||||
it("maps AgentManifest to AgentCreateInput", () => {
|
||||
const input = agentManifestToAgentCreateInput({
|
||||
|
||||
@@ -8,8 +8,6 @@ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync }
|
||||
import { tmpdir } from "node:os";
|
||||
import { isAbsolute, join, normalize, resolve } from "node:path";
|
||||
|
||||
import extractZip from "extract-zip";
|
||||
import { x as tarExtract } from "tar";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
|
||||
import type {
|
||||
@@ -481,6 +479,7 @@ function resolveExtractionRoot(tempDir: string): string {
|
||||
}
|
||||
|
||||
async function extractTarArchive(archivePath: string, outputDir: string): Promise<void> {
|
||||
const { x: tarExtract } = await import("tar");
|
||||
await tarExtract({
|
||||
file: archivePath,
|
||||
cwd: outputDir,
|
||||
@@ -530,6 +529,7 @@ export async function parseCompanyArchive(
|
||||
if (resolvedArchivePath.endsWith(".tar.gz") || resolvedArchivePath.endsWith(".tgz")) {
|
||||
await extractTarArchive(resolvedArchivePath, tempDir);
|
||||
} else if (resolvedArchivePath.endsWith(".zip")) {
|
||||
const extractZip = (await import("extract-zip")).default;
|
||||
await extractZip(resolvedArchivePath, { dir: tempDir });
|
||||
} else {
|
||||
throw new AgentCompaniesParseError(
|
||||
|
||||
Reference in New Issue
Block a user