diff --git a/packages/devtools/src/__tests__/runner.test.ts b/packages/devtools/src/__tests__/runner.test.ts index 3efb401..684e333 100644 --- a/packages/devtools/src/__tests__/runner.test.ts +++ b/packages/devtools/src/__tests__/runner.test.ts @@ -1,8 +1,13 @@ +import { execSync } from "node:child_process"; import { mkdirSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { describe, it, expect } from "vitest"; -import { parseStateFile } from "../desloppify/runner.js"; +import { describe, it, expect, vi, afterEach } from "vitest"; +import { parseStateFile, runDesloppifyScan } from "../desloppify/runner.js"; + +vi.mock("node:child_process", () => ({ + execSync: vi.fn(), +})); describe("parseStateFile", () => { it("parses a valid state file", () => { @@ -43,3 +48,53 @@ describe("parseStateFile", () => { expect(() => parseStateFile(statePath)).toThrow(statePath); }); }); + +describe("runDesloppifyScan scan-loop error handling", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("sets error field when a package scan throws", () => { + vi.mocked(execSync).mockImplementation(() => { + throw new Error("desloppify command not found"); + }); + + const repoDir = join(tmpdir(), `scan-error-test-${Date.now()}`); + const pkgDir = join(repoDir, "packages", "alpha"); + mkdirSync(pkgDir, { recursive: true }); + writeFileSync( + join(pkgDir, "package.json"), + JSON.stringify({ name: "@test/alpha" }), + ); + + const result = runDesloppifyScan({ + repo: repoDir, + packages: ["packages/alpha"], + }); + + expect(result.packages).toHaveLength(1); + expect(result.packages[0]!.error).toContain("desloppify command not found"); + expect(result.packages[0]!.issues).toEqual([]); + }); + + it("does not set error field on a successful scan with zero issues", () => { + vi.mocked(execSync).mockImplementation(() => Buffer.from("")); + + const repoDir = join(tmpdir(), `scan-success-test-${Date.now()}`); + const pkgDir = join(repoDir, "packages", "beta"); + mkdirSync(pkgDir, { recursive: true }); + writeFileSync( + join(pkgDir, "package.json"), + JSON.stringify({ name: "@test/beta" }), + ); + + const result = runDesloppifyScan({ + repo: repoDir, + packages: ["packages/beta"], + }); + + expect(result.packages).toHaveLength(1); + expect(result.packages[0]!.error).toBeUndefined(); + expect(result.packages[0]!.issues).toEqual([]); + }); +}); diff --git a/packages/devtools/src/commands/desloppify.ts b/packages/devtools/src/commands/desloppify.ts index b35933b..05f846d 100644 --- a/packages/devtools/src/commands/desloppify.ts +++ b/packages/devtools/src/commands/desloppify.ts @@ -14,6 +14,9 @@ function formatSummary(result: ScanResult): string { for (const pkg of result.packages) { lines.push(`Package: ${pkg.name} (${pkg.path})`); + if (pkg.error) { + lines.push(` Error: ${pkg.error}`); + } lines.push( ` Score: overall=${pkg.score.overall} objective=${pkg.score.objective} strict=${pkg.score.strict}`, ); diff --git a/packages/devtools/src/desloppify/runner.ts b/packages/devtools/src/desloppify/runner.ts index e508b48..95547e9 100644 --- a/packages/devtools/src/desloppify/runner.ts +++ b/packages/devtools/src/desloppify/runner.ts @@ -268,6 +268,7 @@ export function runDesloppifyScan(options: RunnerOptions): ScanResult { path: pkgRelPath, score: { overall: 0, objective: 0, strict: 0 }, issues: [], + error: String(err), }); } } diff --git a/packages/devtools/src/desloppify/types.ts b/packages/devtools/src/desloppify/types.ts index 94b3642..aed52d0 100644 --- a/packages/devtools/src/desloppify/types.ts +++ b/packages/devtools/src/desloppify/types.ts @@ -21,6 +21,7 @@ export interface PackageResult { strict: number; }; issues: DesloppifyIssue[]; + error?: string; } export interface ScanResult {