Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

49 lines · 2.6 KBRaw History
1import { createHash } from "node:crypto";
2import { readdirSync, readFileSync } from "node:fs";
3import { describe, expect, it } from "vitest";
4import { pluginZipFor } from "../../src/docs/claude-plugins.js";
5import { unzip } from "../../src/lib/archive.js";
6
7const dec = new TextDecoder();
8const dir = new URL("../../claude-plugins/", import.meta.url);
9
10/** The checked-in zip of one plugin (claude-plugins/<name>-<version>.zip), as the build embeds it. */
11function packed(name: string): { bytes: Uint8Array; sha256: string } {
12 const file = readdirSync(dir).find((f) => new RegExp(`^${name}-\\d+\\.\\d+\\.\\d+\\.zip$`).test(f))!;
13 const bytes = new Uint8Array(readFileSync(new URL(file, dir)));
14 return { bytes, sha256: createHash("sha256").update(bytes).digest("hex") };
15}
16
17const files = async (bytes: Uint8Array) =>
18 Object.fromEntries((await unzip(bytes)).map((e) => ["dir" in e ? e.path : e.path, "dir" in e ? "" : dec.decode(e.content)]));
19
20describe("Claude Code plugins served by an instance", () => {
21 for (const name of ["diffinery", "diffinery-ci"]) {
22 it(`${name}: diffinery.com serves the packed zip as it is`, async () => {
23 const p = packed(name);
24 const out = await pluginZipFor("https://diffinery.com", p.bytes, p.sha256);
25 expect(out.bytes).toBe(p.bytes);
26 expect(out.sha256).toBe(p.sha256);
27 });
28
29 it(`${name}: a self-hosted instance defaults the Diffinery URL setting to itself and changes nothing else`, async () => {
30 const p = packed(name);
31 const before = await files(p.bytes);
32 const out = await pluginZipFor("https://git.example.com", p.bytes, p.sha256);
33 expect(out.sha256).toBe(createHash("sha256").update(out.bytes).digest("hex"));
34 const after = await files(out.bytes);
35 expect(Object.keys(after)).toEqual(Object.keys(before));
36 const manifest = JSON.parse(after[".claude-plugin/plugin.json"]!);
37 expect(manifest.userConfig.url.default).toBe("https://git.example.com");
38 expect({ ...manifest, userConfig: undefined }).toEqual({ ...JSON.parse(before[".claude-plugin/plugin.json"]!), userConfig: undefined });
39 for (const f of Object.keys(before)) if (f !== ".claude-plugin/plugin.json") expect(after[f]).toBe(before[f]);
40 // The same instance gets the same bytes every time (a fixed timestamp, as pack-plugins.py writes).
41 expect((await pluginZipFor("https://git.example.com", p.bytes, p.sha256)).sha256).toBe(out.sha256);
42 });
43 }
44
45 it("the diffinery plugin's MCP server follows the setting", async () => {
46 const mcp = JSON.parse((await files(packed("diffinery").bytes))[".mcp.json"]!);
47 expect(mcp.mcpServers.diffinery.url).toBe("${user_config.url}/mcp");
48 });
49});