Merge pull request #11 with Windows wrapper execution and platform tests

This commit is contained in:
Kashyab Ambarani
2026-09-25 03:44:07 -07:00
7 changed files with 170 additions and 27 deletions
+2 -2
View File
@@ -52,8 +52,8 @@ test("checks cache age and future timestamps", () => {
});
test("uses XDG cache directory when configured", () => {
assert.equal(catalogCachePath({ XDG_CACHE_HOME: "/cache" }, "/home/test"), "/cache/pi-devin/models.json");
assert.equal(catalogCachePath({}, "/home/test"), "/home/test/.cache/pi-devin/models.json");
assert.equal(catalogCachePath({ XDG_CACHE_HOME: "/cache" }, "/home/test"), join("/cache", "pi-devin", "models.json"));
assert.equal(catalogCachePath({}, "/home/test"), join("/home/test", ".cache", "pi-devin", "models.json"));
});
for (const [field, value] of Object.entries({ cost_summary: {}, max_context_tokens: "1000", max_output_tokens: -1, model_uid: "", is_beta: "yes" })) {
+5 -3
View File
@@ -8,10 +8,12 @@ import { catalogCachePath, writeCatalogCache } from "../src/catalog-cache.ts";
const directory = fs.mkdtempSync(join(tmpdir(), "pi-devin-catalog-test-"));
after(() => fs.rmSync(directory, { recursive: true, force: true }));
const cli = join(directory, "devin");
const cli = join(directory, process.platform === "win32" ? "devin.cmd" : "devin");
const counter = join(directory, "calls");
const catalog = { families: [{ family_label: "Test", family_uid: "test", slug: "test", variants: [{ model_uid: "test-high", label: "Test high", cost_summary: "$1/MTok In, $2/MTok Out" }] }] };
fs.writeFileSync(cli, `#!${process.execPath}\nconst fs = require('node:fs');\nfs.appendFileSync(${JSON.stringify(counter)}, 'call\\n');\nsetTimeout(() => { console.log(process.env.TEST_CATALOG); process.exit(Number(process.env.TEST_CLI_EXIT || 0)); }, 30);\n`, { mode: 0o700 });
const script = join(directory, "catalog.cjs");
fs.writeFileSync(script, `const fs = require('node:fs');\nfs.appendFileSync(${JSON.stringify(counter)}, 'call\\n');\nsetTimeout(() => { console.log(process.env.TEST_CATALOG); process.exit(Number(process.env.TEST_CLI_EXIT || 0)); }, 30);\n`);
fs.writeFileSync(cli, process.platform === "win32" ? `@echo off\r\n"${process.execPath}" "${script}"\r\n` : `#!/bin/sh\nexec "${process.execPath}" "${script}"\n`, { mode: 0o700 });
const originalEnv = { ...process.env };
process.env.DEVIN_CLI = cli;
const { default: extension } = await import("../extensions/index.ts");
@@ -28,7 +30,7 @@ function setup(t) {
delete process.env.TEST_CLI_EXIT;
fs.writeFileSync(counter, "");
const read = fs.readFileSync, exists = fs.existsSync;
const isCredentials = (path) => String(path).endsWith("devin/credentials.toml");
const isCredentials = (path) => String(path).replaceAll("\\", "/").endsWith("devin/credentials.toml");
t.mock.method(fs, "readFileSync", (path, ...args) => isCredentials(path) ? 'api_key = "synthetic-test-key"\n' : read(path, ...args));
t.mock.method(fs, "existsSync", (path) => isCredentials(path) || exists(path));
t.mock.method(console, "warn", () => {});
+63
View File
@@ -0,0 +1,63 @@
import assert from "node:assert/strict";
import test from "node:test";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { clearDevinBinCache, findDevinBin, whichDevin, runDevin } from "../src/cli.ts";
function fixture(t) {
const directory = mkdtempSync(join(tmpdir(), "pi devin cli "));
const original = { ...process.env };
clearDevinBinCache();
t.after(() => {
for (const key of ["DEVIN_CLI", "LOCALAPPDATA", "ProgramFiles", "PATH"]) {
if (original[key] === undefined) delete process.env[key]; else process.env[key] = original[key];
}
clearDevinBinCache(); rmSync(directory, { recursive: true, force: true });
});
return directory;
}
test("honors an override set after the discovery cache has already been populated", async (t) => {
const directory = fixture(t);
const first = join(directory, "first"), second = join(directory, "second");
writeFileSync(first, ""); writeFileSync(second, "");
process.env.DEVIN_CLI = first; assert.equal(findDevinBin(), first);
process.env.DEVIN_CLI = second; assert.equal(await whichDevin(), second);
});
test("runs a native executable directly with argument boundaries intact", async (t) => {
fixture(t); process.env.DEVIN_CLI = process.execPath;
const value = 'spaces & punctuation "stay literal"';
const result = await runDevin(["-e", "process.stdout.write(process.argv[1])", value]);
assert.equal(result.code, 0); assert.equal(result.stdout, value);
});
test("finds the Windows installer binary and ignores a directory override", { skip: process.platform !== "win32" }, (t) => {
const directory = fixture(t); process.env.LOCALAPPDATA = directory;
process.env.DEVIN_CLI = directory;
const binary = join(directory, "devin", "cli", "bin", "devin.exe");
mkdirSync(dirname(binary), { recursive: true }); writeFileSync(binary, "");
assert.equal(findDevinBin(), binary);
});
test("Windows PATH lookup uses where.exe and reads multiple results", { skip: process.platform !== "win32" }, async (t) => {
const directory = fixture(t); delete process.env.DEVIN_CLI;
process.env.LOCALAPPDATA = directory; process.env.ProgramFiles = directory;
const bin = join(directory, "path bin"); mkdirSync(bin);
const binary = join(bin, "devin.exe"); writeFileSync(binary, "");
process.env.PATH = `${bin};${process.env.PATH}`;
assert.equal(await whichDevin(), binary);
});
test("Windows batch wrappers work in paths with spaces, including inherited stdio", { skip: process.platform !== "win32" }, async (t) => {
const directory = fixture(t), script = join(directory, "echo.cjs"), wrapper = join(directory, "devin.cmd");
writeFileSync(script, 'process.stdout.write(JSON.stringify(process.argv.slice(2)));');
writeFileSync(wrapper, `@echo off\r\n"${process.execPath}" "${script}" %*\r\n`);
process.env.DEVIN_CLI = wrapper;
const result = await runDevin(["models", "list", "--format", "json", "a b"]);
assert.equal(result.code, 0, result.stderr);
assert.deepEqual(JSON.parse(result.stdout), ["models", "list", "--format", "json", "a b"]);
assert.equal((await runDevin(["version"], { inheritStdio: true })).code, 0);
await assert.rejects(runDevin(["%PATH%"]), /Unsupported shell characters/);
});