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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,13 @@ remotely exploitable Brotli and HTTP client vulnerabilities).
npm ci
npm run typecheck
npm run check:boundary
npm run build # tests spawn the compiled daemon and hooks
npm test # builds first; tests spawn the compiled daemon and hooks
npm run plugins:check
npm test
```

`npm test` works after a clean checkout and `npm ci`; no separate build is needed.
Run `npm run build` before invoking Vitest or the plugin checks directly.

## Releases

```bash
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.json",
"typecheck": "tsc -p tsconfig.json --noEmit",
"pretest": "npm run build",
"test": "vitest run",
"check:boundary": "node scripts/check-boundary.mjs",
"plugins:generate": "node scripts/host-plugins.mjs",
Expand Down
25 changes: 17 additions & 8 deletions tests/runtime/native-host-plugins.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "fs";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "fs";
import * as adapterUtils from "../../src/runtime/adapter-utils";
import { execFileSync } from "child_process";
import os from "os";
import path from "path";
Expand Down Expand Up @@ -104,13 +105,21 @@ describe("native host installation", () => {
});
it("rolls back host files if writing runtime configuration fails", async () => {
const dir = temporary(), home = path.join(dir, "runtime"), hostHome = path.join(dir, "host");
const configDir = path.join(home, "config", "native-hosts");
mkdirSync(configDir, { recursive: true }); chmodSync(configDir, 0o500);
try {
await expect(installNativeHostAdapter("opencode", { home, hostHome })).rejects.toThrow();
expect(existsSync(path.join(hostHome, "plugins", "crosshead.js"))).toBe(false);
expect(listAdapterRecords(resolveRuntimePaths(home))).toEqual([]);
} finally { chmodSync(configDir, 0o700); }
const configFile = path.join(home, "config", "native-hosts", "opencode.json");
const pluginFile = path.join(hostHome, "plugins", "crosshead.js");
const writeManagedFile = adapterUtils.writeManagedFile;
// Inject a real write boundary failure: chmod cannot deny writes to root.
vi.spyOn(adapterUtils, "writeManagedFile").mockImplementation((file, ...args) => {
if (file === configFile) {
expect(readFileSync(pluginFile, "utf8")).toContain("crosshead-native-host-v1");
throw Object.assign(new Error("EACCES: runtime configuration write denied"), { code: "EACCES" });
}
return writeManagedFile(file, ...args);
});
await expect(installNativeHostAdapter("opencode", { home, hostHome })).rejects.toThrow("EACCES");
expect(existsSync(pluginFile)).toBe(false);
expect(existsSync(configFile)).toBe(false);
expect(listAdapterRecords(resolveRuntimePaths(home))).toEqual([]);
});
it("does not remove a replacement plugin on uninstall", async () => {
const dir = temporary(), result = await installNativeHostAdapter("opencode", { home: path.join(dir, "runtime"), hostHome: path.join(dir, "host") });
Expand Down
Loading