Skip to content

Commit

Permalink
[LSP] Attempt to detect local turbo path from package manager as fall…
Browse files Browse the repository at this point in the history
…back (#7662)

As identified in #7425, we can't always assume the people are able to install turbo globally, meaning we can't rely on turbo's build-in resolution in call cases. As a fallback, this will attempt to resolve the path from your package manager. It will _still_ surface a prompt to install global turbo however, unless you silence the recommendation with the "Use Local Turbo" setting. 

Also bundled in with this change is an attempt to support asdf as well by sourcing its script before finding the turbo executable.

Closes TURBO-2564
  • Loading branch information
arlyon authored Mar 8, 2024
1 parent 1e25520 commit 57cdfac
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 20 deletions.
6 changes: 6 additions & 0 deletions packages/turbo-vsc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
"required": false,
"default": null,
"description": "The path to your global `turbo` executable, if you'd rather not rely on auto-detection."
},
"turbo.useLocalTurbo": {
"type": "boolean",
"required": false,
"default": false,
"description": "Silence the 'install global turbo' prompt and always use local turbo."
}
}
}
Expand Down
106 changes: 86 additions & 20 deletions packages/turbo-vsc/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,39 @@ const pipelineColors = [...Array(10).keys()].map(rainbowRgb).map((color) =>
const refreshDecorations = useDebounce(updateJSONDecorations, 1000);

export function activate(context: ExtensionContext) {
const options: cp.ExecOptions = {
const options: cp.ExecSyncOptionsWithStringEncoding = {
cwd: workspace.workspaceFolders?.[0].uri.path,
encoding: "utf8",
};

let turboPath = workspace.getConfiguration("turbo").get("path");
const turboSettings = workspace.getConfiguration("turbo");
let turboPath: string | undefined = turboSettings.get("path");
const useLocalTurbo: boolean = turboSettings.get("useLocalTurbo") ?? false;

if (turboPath && !fs.existsSync(turboPath)) {
window.showErrorMessage(
`turbo does not exist at path \`${turboPath}\`, attempting to locate it`
);
turboPath = undefined;
}

try {
if (turboPath == null) {
turboPath = cp.execSync(
"bash -c 'source ~/.nvm/nvm.sh; which turbo'",
// attempt to source two well known version managers
'sh -c \'source "$HOME/.nvm/nvm.sh" > /dev/null 2>&1; source "$HOME/.asdf/asdf.sh" > /dev/null 2>&1; which turbo\'',
options
);
}
} catch (e: any) {
if (
e.message.includes("command not found") ||
e.message.includes("Command failed")
e.message.includes("Command failed") ||
e.message.includes("which: no turbo in")
) {
promptGlobalTurbo();
// attempt to find local turbo instead
promptGlobalTurbo(useLocalTurbo);
turboPath = findLocalTurbo();
} else {
window.showErrorMessage(e.message);
}
Expand All @@ -90,7 +105,7 @@ export function activate(context: ExtensionContext) {
cp.exec(`${turboPath} daemon start`, options, (err) => {
if (err) {
if (err.message.includes("command not found")) {
promptGlobalTurbo();
promptGlobalTurbo(useLocalTurbo);
} else {
window.showErrorMessage(JSON.stringify(err));
}
Expand All @@ -107,7 +122,7 @@ export function activate(context: ExtensionContext) {
cp.exec(`${turboPath} daemon stop`, options, (err) => {
if (err) {
if (err.message.includes("command not found")) {
promptGlobalTurbo();
promptGlobalTurbo(useLocalTurbo);
} else {
window.showErrorMessage(err.message);
}
Expand All @@ -124,7 +139,7 @@ export function activate(context: ExtensionContext) {
cp.exec(`${turboPath} daemon status`, options, (err) => {
if (err) {
if (err.message.includes("command not found")) {
promptGlobalTurbo();
promptGlobalTurbo(useLocalTurbo);
} else {
window.showErrorMessage(err.message);
updateStatusBarItem(false);
Expand All @@ -139,7 +154,7 @@ export function activate(context: ExtensionContext) {

context.subscriptions.push(
commands.registerCommand("turbo.run", (args) => {
let terminal = window.createTerminal({
const terminal = window.createTerminal({
name: `${args}`,
isTransient: true,
iconPath: Uri.joinPath(context.extensionUri, "resources", "icon.svg"),
Expand All @@ -151,7 +166,7 @@ export function activate(context: ExtensionContext) {

context.subscriptions.push(
commands.registerCommand("turbo.codemod", (args) => {
let terminal = window.createTerminal({
const terminal = window.createTerminal({
name: "Turbo Codemod",
isTransient: true,
iconPath: Uri.joinPath(context.extensionUri, "resources", "icon.svg"),
Expand All @@ -163,16 +178,16 @@ export function activate(context: ExtensionContext) {

context.subscriptions.push(
commands.registerCommand("turbo.install", (args) => {
let terminal = window.createTerminal({
const terminal = window.createTerminal({
name: "Install Turbo",
isTransient: true,
iconPath: Uri.joinPath(context.extensionUri, "resources", "icon.svg"),
});
terminal.sendText(`npm i -g turbo && exit`);
terminal.sendText("npm i -g turbo && exit");
terminal.show();

return new Promise((resolve) => {
let dispose = window.onDidCloseTerminal((terminal) => {
const dispose = window.onDidCloseTerminal((terminal) => {
if (terminal.name === "Install Turbo") {
dispose.dispose();
resolve(terminal.exitStatus?.code);
Expand Down Expand Up @@ -216,7 +231,7 @@ export function activate(context: ExtensionContext) {
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used

let lspPath = Uri.joinPath(
const lspPath = Uri.joinPath(
context.extensionUri,
"out",
`turborepo-lsp-${process.platform}-${process.arch}${
Expand Down Expand Up @@ -270,7 +285,7 @@ export function deactivate(): Thenable<void> | undefined {

function updateStatusBarItem(running: boolean) {
toolbar.command = running ? "turbo.daemon.stop" : "turbo.daemon.start";
toolbar.text = running ? `turbo Running` : "turbo Stopped";
toolbar.text = running ? "turbo Running" : "turbo Stopped";
toolbar.show();
}

Expand All @@ -291,7 +306,7 @@ function updateJSONDecorations(editor?: TextEditor) {
if (property === "pipeline") {
isPipelineKey = true;
for (let i = 1; i < 9; i++) {
let index = i + offset;
const index = i + offset;
editor.setDecorations(pipelineColors[i], [
new Range(
editor.document.positionAt(index),
Expand Down Expand Up @@ -326,21 +341,25 @@ function updateJSONDecorations(editor?: TextEditor) {
});
}

async function promptGlobalTurbo() {
let answer = await window.showErrorMessage(
async function promptGlobalTurbo(useLocalTurbo: boolean) {
if (useLocalTurbo) {
return;
}

const answer = await window.showErrorMessage(
"turbo not found. Please see the docs to install, or set the path manually in the settings.",
"Install Now",
"Open Docs",
"Open Settings"
);

if (answer === "Install Now") {
let exitCode = await commands.executeCommand("turbo.install");
const exitCode = await commands.executeCommand("turbo.install");
if (exitCode === 0) {
window.showInformationMessage("turbo installed");
await commands.executeCommand("turbo.daemon.start");
} else {
let message = await window.showErrorMessage(
const message = await window.showErrorMessage(
"Unable to install turbo. Please install manually.",
"Open Docs"
);
Expand All @@ -355,3 +374,50 @@ async function promptGlobalTurbo() {
commands.executeCommand("workbench.action.openSettings", "turbo.path");
}
}

function findLocalTurbo(): string | undefined {
const options: cp.ExecSyncOptionsWithStringEncoding = {
encoding: "utf8",
cwd: workspace.workspaceFolders?.[0].uri.path,
};

const checks = [
() => {
const npmList = cp.execSync("npm ls turbo --json", options);
const npmData = JSON.parse(npmList);

// this is relative to node_modules
const packagePath = npmData?.dependencies?.turbo?.resolved;

const PREFIX = "file:"; // npm ls returns a file: prefix

if (packagePath?.startsWith(PREFIX)) {
return path.join(
"node_modules",
packagePath.slice(PREFIX.length),
"bin",
"turbo"
);
}
},
() => {
const turboBin = cp.execSync("yarn bin turbo", options);
return turboBin.trim();
},
() => {
const binFolder = cp.execSync("pnpm bin", options).trim();
return path.join(binFolder, "turbo");
},
];

for (const potentialPath of checks) {
try {
const potential = potentialPath();
if (potential && fs.existsSync(potential)) {
return potential;
}
} catch (e) {
// no-op
}
}
}

0 comments on commit 57cdfac

Please sign in to comment.