Skip to content
Draft
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
3 changes: 0 additions & 3 deletions extensions/vscode/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Credentials } from "./resources/Credentials";
import { ContentRecords } from "./resources/ContentRecords";
import { Configurations } from "./resources/Configurations";
import { Files } from "./resources/Files";
import { Interpreters } from "./resources/Interpreters";
import { Packages } from "./resources/Packages";
import { Secrets } from "./resources/Secrets";
import { SnowflakeConnections } from "./resources/SnowflakeConnections";
Expand All @@ -20,7 +19,6 @@ class PublishingClientApi {
private client;

configurations: Configurations;
interpreters: Interpreters;
credentials: Credentials;
contentRecords: ContentRecords;
files: Files;
Expand Down Expand Up @@ -57,7 +55,6 @@ class PublishingClientApi {
this.credentials = new Credentials(this.client);
this.contentRecords = new ContentRecords(this.client);
this.files = new Files(this.client);
this.interpreters = new Interpreters(this.client);
this.packages = new Packages(this.client);
this.secrets = new Secrets(this.client);
this.integrationRequests = new IntegrationRequests(this.client);
Expand Down
31 changes: 0 additions & 31 deletions extensions/vscode/src/api/resources/Interpreters.ts

This file was deleted.

164 changes: 164 additions & 0 deletions extensions/vscode/src/interpreters/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (C) 2026 by Posit Software, PBC.

import { beforeEach, describe, expect, test, vi } from "vitest";
import { getInterpreterDefaults } from "./index";

const { mockDetectPython, mockDetectR } = vi.hoisted(() => ({
mockDetectPython: vi.fn(),
mockDetectR: vi.fn(),
}));

vi.mock("./pythonInterpreter", () => ({
detectPythonInterpreter: mockDetectPython,
}));

vi.mock("./rInterpreter", () => ({
detectRInterpreter: mockDetectR,
}));

describe("getInterpreterDefaults", () => {
beforeEach(() => {
mockDetectPython.mockReset();
mockDetectR.mockReset();
});

test("returns results when both detections succeed", async () => {
mockDetectPython.mockResolvedValue({
config: {
version: "3.11.5",
packageFile: "requirements.txt",
packageManager: "auto",
},
preferredPath: "/usr/bin/python3",
});
mockDetectR.mockResolvedValue({
config: {
version: "4.3.2",
packageFile: "renv.lock",
packageManager: "renv",
},
preferredPath: "/usr/bin/R",
});

const result = await getInterpreterDefaults(
"/project",
"/usr/bin/python3",
"/usr/bin/R",
);

expect(result.python).toEqual({
version: "3.11.5",
packageFile: "requirements.txt",
packageManager: "auto",
});
expect(result.preferredPythonPath).toBe("/usr/bin/python3");
expect(result.r).toEqual({
version: "4.3.2",
packageFile: "renv.lock",
packageManager: "renv",
});
expect(result.preferredRPath).toBe("/usr/bin/R");
});

test("returns empty Python config when Python detection rejects", async () => {
mockDetectPython.mockRejectedValue(new Error("python exploded"));
mockDetectR.mockResolvedValue({
config: {
version: "4.3.2",
packageFile: "renv.lock",
packageManager: "renv",
},
preferredPath: "/usr/bin/R",
});

const result = await getInterpreterDefaults(
"/project",
"/usr/bin/python3",
"/usr/bin/R",
);

expect(result.python).toEqual({
version: "",
packageFile: "",
packageManager: "",
});
expect(result.preferredPythonPath).toBe("/usr/bin/python3");
// R still succeeds
expect(result.r.version).toBe("4.3.2");
});

test("returns empty R config when R detection rejects", async () => {
mockDetectPython.mockResolvedValue({
config: {
version: "3.11.5",
packageFile: "requirements.txt",
packageManager: "auto",
},
preferredPath: "/usr/bin/python3",
});
mockDetectR.mockRejectedValue(new Error("R exploded"));

const result = await getInterpreterDefaults(
"/project",
"/usr/bin/python3",
"/usr/bin/R",
);

// Python still succeeds
expect(result.python.version).toBe("3.11.5");
expect(result.r).toEqual({
version: "",
packageFile: "",
packageManager: "",
});
expect(result.preferredRPath).toBe("/usr/bin/R");
});

test("returns empty configs when both detections reject", async () => {
mockDetectPython.mockRejectedValue(new Error("python exploded"));
mockDetectR.mockRejectedValue(new Error("R exploded"));

const result = await getInterpreterDefaults(
"/project",
"/usr/bin/python3",
"/usr/bin/R",
);

expect(result.python).toEqual({
version: "",
packageFile: "",
packageManager: "",
});
expect(result.r).toEqual({
version: "",
packageFile: "",
packageManager: "",
});
});

test("handles undefined paths in rejection fallback", async () => {
mockDetectPython.mockRejectedValue(new Error("fail"));
mockDetectR.mockRejectedValue(new Error("fail"));

const result = await getInterpreterDefaults("/project");

expect(result.preferredPythonPath).toBe("");
expect(result.preferredRPath).toBe("");
});

test("passes paths through to detectors", async () => {
mockDetectPython.mockResolvedValue({
config: { version: "", packageFile: "", packageManager: "" },
preferredPath: "",
});
mockDetectR.mockResolvedValue({
config: { version: "", packageFile: "", packageManager: "" },
preferredPath: "",
});

await getInterpreterDefaults("/project", "/my/python", "/my/R");

expect(mockDetectPython).toHaveBeenCalledWith("/project", "/my/python");
expect(mockDetectR).toHaveBeenCalledWith("/project", "/my/R");
});
});
43 changes: 43 additions & 0 deletions extensions/vscode/src/interpreters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (C) 2026 by Posit Software, PBC.

import { InterpreterDefaults } from "src/api/types/interpreters";
import { detectPythonInterpreter } from "./pythonInterpreter";
import { detectRInterpreter } from "./rInterpreter";

/**
* Detect Python and R interpreter defaults for a project directory.
* Runs both detections concurrently via Promise.allSettled.
*/
export async function getInterpreterDefaults(
projectDir: string,
pythonPath?: string,
rPath?: string,
): Promise<InterpreterDefaults> {
const [pythonResult, rResult] = await Promise.allSettled([
detectPythonInterpreter(projectDir, pythonPath),
detectRInterpreter(projectDir, rPath),
]);

const python =
pythonResult.status === "fulfilled"
? pythonResult.value
: {
config: { version: "", packageFile: "", packageManager: "" },
preferredPath: pythonPath || "",
};

const r =
rResult.status === "fulfilled"
? rResult.value
: {
config: { version: "", packageFile: "", packageManager: "" },
preferredPath: rPath || "",
};

return {
python: python.config,
preferredPythonPath: python.preferredPath,
r: r.config,
preferredRPath: r.preferredPath,
};
}
Loading
Loading