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
12 changes: 12 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Normalize line endings to LF across all platforms
* text=auto eol=lf

# Binary files should not be normalized
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.woff binary
*.woff2 binary
*.eot binary
*.ttf binary
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
- windows-latest
- macos-latest
node-version:
- "20"
- "22"
- "24"

Expand Down
95 changes: 95 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"format:check": "prettier --check 'src/**/*.{ts,tsx}'",
"check": "npm run typecheck && npm run lint && npm run format:check",
"build": "npm run check && npm run bundle && node -e \"require('fs').chmodSync('dist/cli.js', 0o755)\"",
"test": "tsx --test src/tests/*.test.ts",
"test": "node src/tests/run-tests.mjs",
"test:single": "tsx --test",
"prepack": "npm run build",
"prepare": "husky"
Expand All @@ -58,6 +58,7 @@
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"glob": "^13.0.6",
"husky": "^9.1.7",
"lint-staged": "^17.0.4",
"prettier": "^3.8.3",
Expand Down
8 changes: 4 additions & 4 deletions src/mcp/mcp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ export class McpClient {
const isWindows = os.platform() === "win32";

if (isWindows) {
// On Windows, .cmd files require shell: true to be spawned.
// Build a single command string so cmd.exe handles quoting correctly.
const cmd = [this.command + ".cmd", ...args].join(" ");
this.process = spawn(cmd, [], {
// On Windows, shell: true lets cmd.exe resolve the command via
// PATHEXT (npx → npx.cmd, etc.) without blindly appending .cmd,
// which would break absolute paths like process.execPath.
this.process = spawn(this.command, args, {
stdio: ["pipe", "pipe", "pipe"],
env: childEnv,
shell: true,
Expand Down
72 changes: 38 additions & 34 deletions src/tests/clipboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,44 @@ test("readClipboardImage returns null when no clipboard helpers are installed",
assert.equal(result, null);
});

test("readClipboardImage uses osascript fallback on macOS when pngpaste is missing", async () => {
const binDir = fs.mkdtempSync(path.join(os.tmpdir(), "deepcode-clipboard-test-bin-"));
try {
fs.writeFileSync(path.join(binDir, "pngpaste"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
fs.writeFileSync(
path.join(binDir, "osascript"),
[
"#!/bin/sh",
'for arg in "$@"; do',
' case "$arg" in',
" *'open for access POSIX file " + '"' + "'*)",
' path_part=${arg#*POSIX file \\"}',
' out_path=${path_part%%\\"*}',
' printf fakepng > "$out_path"',
" exit 0",
" ;;",
" esac",
"done",
"exit 1",
"",
].join("\n"),
{ mode: 0o755 }
);
test(
"readClipboardImage uses osascript fallback on macOS when pngpaste is missing",
{ skip: process.platform === "win32" },
async () => {
const binDir = fs.mkdtempSync(path.join(os.tmpdir(), "deepcode-clipboard-test-bin-"));
try {
fs.writeFileSync(path.join(binDir, "pngpaste"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
fs.writeFileSync(
path.join(binDir, "osascript"),
[
"#!/bin/sh",
'for arg in "$@"; do',
' case "$arg" in',
" *'open for access POSIX file " + '"' + "'*)",
' path_part=${arg#*POSIX file \\"}',
' out_path=${path_part%%\\"*}',
' printf fakepng > "$out_path"',
" exit 0",
" ;;",
" esac",
"done",
"exit 1",
"",
].join("\n"),
{ mode: 0o755 }
);

const moduleUrl = new URL(`../ui/clipboard.ts?t=${Date.now()}`, import.meta.url).href;
const { readClipboardImage } = (await import(moduleUrl)) as ClipboardModule;
const moduleUrl = new URL(`../ui/clipboard.ts?t=${Date.now()}`, import.meta.url).href;
const { readClipboardImage } = (await import(moduleUrl)) as ClipboardModule;

process.env.PATH = binDir;
const result = withPlatform("darwin", () => readClipboardImage());
assert.equal(result?.mimeType, "image/png");
assert.equal(result?.dataUrl, `data:image/png;base64,${Buffer.from("fakepng").toString("base64")}`);
} finally {
process.env.PATH = ORIGINAL_PATH;
Object.defineProperty(process, "platform", { value: ORIGINAL_PLATFORM });
fs.rmSync(binDir, { recursive: true, force: true });
process.env.PATH = binDir;
const result = withPlatform("darwin", () => readClipboardImage());
assert.equal(result?.mimeType, "image/png");
assert.equal(result?.dataUrl, `data:image/png;base64,${Buffer.from("fakepng").toString("base64")}`);
} finally {
process.env.PATH = ORIGINAL_PATH;
Object.defineProperty(process, "platform", { value: ORIGINAL_PLATFORM });
fs.rmSync(binDir, { recursive: true, force: true });
}
}
});
);
13 changes: 13 additions & 0 deletions src/tests/run-tests.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Cross-platform test runner: finds all *.test.ts files and runs them via tsx.
// Uses the glob package for reliable cross-platform pattern expansion (Node 20+).
/* eslint-disable */

import { globSync } from "glob";
import { spawnSync } from "child_process";

const cwd = new URL("../..", import.meta.url);
const testFiles = globSync("src/tests/*.test.ts", { cwd });

const result = spawnSync(process.execPath, ["--import", "tsx", "--test", ...testFiles], { stdio: "inherit", cwd });

process.exit(result.status ?? 1);
Loading
Loading