File size: 3,872 Bytes
c1243f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";

const copyToClipboard = vi.fn();
const runtime = {
  log: vi.fn(),
  error: vi.fn(),
  exit: vi.fn(),
};

vi.mock("../infra/clipboard.js", () => ({
  copyToClipboard,
}));

vi.mock("../runtime.js", () => ({
  defaultRuntime: runtime,
}));

function writeManifest(dir: string) {
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(path.join(dir, "manifest.json"), JSON.stringify({ manifest_version: 3 }));
}

describe("bundled extension resolver", () => {
  it("walks up to find the assets directory", async () => {
    const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ext-root-"));
    const here = path.join(root, "dist", "cli");
    const assets = path.join(root, "assets", "chrome-extension");

    try {
      writeManifest(assets);
      fs.mkdirSync(here, { recursive: true });

      const { resolveBundledExtensionRootDir } = await import("./browser-cli-extension.js");
      expect(resolveBundledExtensionRootDir(here)).toBe(assets);
    } finally {
      fs.rmSync(root, { recursive: true, force: true });
    }
  });

  it("prefers the nearest assets directory", async () => {
    const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ext-root-"));
    const here = path.join(root, "dist", "cli");
    const distAssets = path.join(root, "dist", "assets", "chrome-extension");
    const rootAssets = path.join(root, "assets", "chrome-extension");

    try {
      writeManifest(distAssets);
      writeManifest(rootAssets);
      fs.mkdirSync(here, { recursive: true });

      const { resolveBundledExtensionRootDir } = await import("./browser-cli-extension.js");
      expect(resolveBundledExtensionRootDir(here)).toBe(distAssets);
    } finally {
      fs.rmSync(root, { recursive: true, force: true });
    }
  });
});

describe("browser extension install", () => {
  it("installs into the state dir (never node_modules)", async () => {
    const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ext-"));

    try {
      const { installChromeExtension } = await import("./browser-cli-extension.js");
      const sourceDir = path.resolve(process.cwd(), "assets/chrome-extension");
      const result = await installChromeExtension({ stateDir: tmp, sourceDir });

      expect(result.path).toBe(path.join(tmp, "browser", "chrome-extension"));
      expect(fs.existsSync(path.join(result.path, "manifest.json"))).toBe(true);
      expect(result.path.includes("node_modules")).toBe(false);
    } finally {
      fs.rmSync(tmp, { recursive: true, force: true });
    }
  });

  it("copies extension path to clipboard", async () => {
    const prev = process.env.OPENCLAW_STATE_DIR;
    const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ext-path-"));
    process.env.OPENCLAW_STATE_DIR = tmp;

    try {
      copyToClipboard.mockReset();
      copyToClipboard.mockResolvedValue(true);
      runtime.log.mockReset();
      runtime.error.mockReset();
      runtime.exit.mockReset();

      const dir = path.join(tmp, "browser", "chrome-extension");
      writeManifest(dir);

      const { Command } = await import("commander");
      const { registerBrowserExtensionCommands } = await import("./browser-cli-extension.js");

      const program = new Command();
      const browser = program.command("browser").option("--json", false);
      registerBrowserExtensionCommands(
        browser,
        (cmd) => cmd.parent?.opts?.() as { json?: boolean },
      );

      await program.parseAsync(["browser", "extension", "path"], { from: "user" });

      expect(copyToClipboard).toHaveBeenCalledWith(dir);
    } finally {
      if (prev === undefined) {
        delete process.env.OPENCLAW_STATE_DIR;
      } else {
        process.env.OPENCLAW_STATE_DIR = prev;
      }
    }
  });
});