Skip to content

Commit

Permalink
Merge branch 'main' into mark/version-deterministic
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMcCulloh authored Sep 11, 2023
2 parents a1cd61d + 466955f commit ca5f17a
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 142 deletions.
3 changes: 1 addition & 2 deletions apps/vscode-wing/.projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,8 @@ const contributes: VSCodeExtensionContributions = {
properties: {
"wing.bin": {
type: "string",
default: "wing",
description:
"Path to the Wing binary. Will be `wing` from PATH by default.\nSet to `npx` to automatically retrieve the version that matches this extension",
"Path to the Wing binary. Will be `wing` from PATH by default.",
},
},
},
Expand Down
7 changes: 3 additions & 4 deletions apps/vscode-wing/package.json

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

174 changes: 94 additions & 80 deletions apps/vscode-wing/src/bin-helper.ts
Original file line number Diff line number Diff line change
@@ -1,125 +1,139 @@
import { exec, execSync } from "child_process";
import { exec } from "child_process";
import { realpath } from "fs/promises";
import { promisify } from "node:util";
import { basename, join } from "path";
import { env } from "process";
import {
StatusBarItem,
ExtensionContext,
ProgressLocation,
window,
workspace,
} from "vscode";
import { StatusBarItem, ProgressLocation, window, workspace } from "vscode";
import which from "which";
import { CFG_WING, CFG_WING_BIN } from "./constants";
import { Loggers } from "./logging";

let STATUS_BAR_ITEM: StatusBarItem;

/**
* Install wing globally using npm if desired by the user
*
* @returns "wing" if wing is successfully installed, "npx" otherwise
* @returns true if wing is successfully installed, false otherwise
*/
async function guidedWingInstallation(version: string) {
async function guidedWingInstallation() {
return window.withProgress(
{
location: ProgressLocation.Notification,
cancellable: false,
},
async (progress) => {
progress.report({ message: `Installing Wing v${version}...` });
return new Promise((resolve, reject) => {
exec(`npm install -g winglang@${version}`, (error, stdout) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
})
progress.report({ message: `Installing Wing...` });
return executeCommand("npm install -g winglang@latest")
.then(() => {
void window.showInformationMessage(
`Wing v${version} has been installed!`
);
return "wing";
void window.showInformationMessage(`Wing has been installed!`);
return true;
})
.catch((e) => {
void window.showErrorMessage(
`Failed to install Wing v${version}: ${e}`
);
return "npx";
void window.showErrorMessage(`Failed to install Wing: ${e}`);
return false;
});
}
);
}

async function updateStatusBar(wingBin: string, args?: string[]) {
let clean_args = args ? [...args] : [];
clean_args.push("-V");

// get current wing version
const version = await new Promise<string>((resolve, reject) => {
exec(`${wingBin} ${clean_args.join(" ")}`, (error, stdout) => {
if (error) {
reject(error);
} else {
resolve(stdout.trim());
}
});
}).catch(() => "unknown");

if (version === "unknown") {
return;
export async function updateStatusBar(wingBin: string) {
let version = "not found";
try {
version = await executeCommand(`"${wingBin}" -V --no-update-check`);
} catch (e) {
Loggers.default.appendLine(
`Failed to get wing version from ${wingBin}: ${e}`
);
}

// update status bar
const status = `Wing v${version}`;
const status = `Wing (${version})`;
if (!STATUS_BAR_ITEM) {
STATUS_BAR_ITEM = window.createStatusBarItem();
}

STATUS_BAR_ITEM.text = status;
STATUS_BAR_ITEM.tooltip = wingBin;
STATUS_BAR_ITEM.show();
}

/**
* Get the absolute location of the wing executable
*
* @param command The command to search for, defaults to "wing"
* @returns The absolute path to the wing executable, or null if not found/installed
*/
function wingBinaryLocation(command?: string) {
return which.sync(command ?? "wing", { nothrow: true });
return version !== "not found";
}

export async function getWingBinAndArgs(context: ExtensionContext) {
const extVersion = context.extension.packageJSON.version;
const configuredWingBin = workspace
.getConfiguration(CFG_WING)
.get<string>(CFG_WING_BIN, "wing");
let wingBin = env.WING_BIN ?? configuredWingBin;

if (wingBin !== "npx") {
const result = wingBinaryLocation(wingBin);
if (!result) {
const npmInstallOption = `Install globally with npm`;
const choice = await window.showWarningMessage(
`"${wingBin}" is not in PATH, please choose one of the following options to use the Wing language server`,
npmInstallOption
);
export async function getWingBin(): Promise<string | null> {
let configuredWingBin =
env.WING_BIN ??
workspace.getConfiguration(CFG_WING).get<string>(CFG_WING_BIN, "wing");

configuredWingBin = configuredWingBin.trim();

if (!configuredWingBin) {
configuredWingBin = "wing";
}

const configuredPath = await resolvePath(configuredWingBin);
if (configuredPath) {
// this is already a path, so we can just return it
return configuredPath;
}

try {
const whichPath = await which(configuredWingBin);
Loggers.default.appendLine(
`"which ${configuredWingBin}" => "${whichPath}"`
);
return resolvePath(whichPath);
} catch (e) {
const choice = await window.showWarningMessage(
`Unable to find wing from "${configuredWingBin}" (not in PATH). Install globally with \`npm install -g winglang@latest\`? (${e})`,
"Yes!"
);

if (choice === npmInstallOption) {
wingBin = await guidedWingInstallation(extVersion);
if (choice === "Yes!") {
if (await guidedWingInstallation()) {
return getWingBin();
} else {
// User decided to ignore the warning
return;
return null;
}
} else {
// User decided to ignore the warning
return null;
}
}
}

export async function resolvePath(p: string) {
try {
const real = await realpath(p);

if (basename(real) === "volta-shim") {
// Handle volta shims by resolving the true path
// https://docs.volta.sh/guide/#how-does-it-work
const voltaPath = join(real, "..", "volta");
const vResult = await executeCommand(`"${voltaPath}" which wing`).catch(
async () => {
void window.showWarningMessage(
`Failed to resolve volta from shim: ${real}`
);
return null;
}
);

const args =
wingBin === "npx"
? ["-y", "-q", `winglang@${extVersion}`, "--no-update-check"]
: ["--no-update-check"];
if (!vResult) {
return null;
} else {
return resolvePath(vResult);
}
}

await updateStatusBar(wingBin, args);
return real;
} catch (e) {
// the path doesn't exist or is invalid
return null;
}
}

return [wingBin, ...args];
export async function executeCommand(command: string): Promise<string> {
return promisify(exec)(command, {
encoding: "utf-8",
}).then((out) => out.stdout.trim());
}
27 changes: 18 additions & 9 deletions apps/vscode-wing/src/console/console-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
window,
WebviewPanel,
commands,
OutputChannel,
TreeView,
ViewColumn,
Uri,
Expand All @@ -21,6 +20,7 @@ import {
} from "./explorer-providers/TestsExplorerProvider";
import { type Client } from "./services/client";
import { VIEW_TYPE_CONSOLE } from "../constants";
import { Loggers } from "../logging";

export interface ConsoleInstance {
id: string;
Expand All @@ -37,11 +37,11 @@ export interface ConsoleManager {
setActiveInstance: (instanceId: string) => Promise<void>;
activeInstances: () => boolean;
getActiveInstanceId: () => string | undefined;
stopAll: () => Promise<void>;
}

export const createConsoleManager = (
context: ExtensionContext,
logger: OutputChannel
context: ExtensionContext
): ConsoleManager => {
const instances: Record<string, ConsoleInstance> = {};
const resourcesExplorer = new ResourcesExplorerProvider();
Expand Down Expand Up @@ -115,7 +115,7 @@ export const createConsoleManager = (
const logs = await instance.client.getLogs({ time: logsTimestamp });

logs.forEach((log) => {
logger.appendLine(`[${log.level}] ${log.message}`);
Loggers.console.appendLine(`[${log.level}] ${log.message}`);
});
logsTimestamp = Date.now();
};
Expand All @@ -136,7 +136,9 @@ export const createConsoleManager = (
};

const addInstance = async (instance: ConsoleInstance) => {
logger.appendLine(`Wing Console is running at http://${instance.url}`);
Loggers.console.appendLine(
`Wing Console is running at http://${instance.url}`
);

instance.client.onInvalidateQuery({
onData: async (key) => {
Expand All @@ -155,7 +157,7 @@ export const createConsoleManager = (
}, 300);
},
onError: (err) => {
logger.appendLine(err);
Loggers.console.appendLine(err);
},
});

Expand Down Expand Up @@ -190,7 +192,7 @@ export const createConsoleManager = (
});
},
onError: (err) => {
logger.appendLine(err);
Loggers.console.appendLine(err);
},
});

Expand Down Expand Up @@ -235,7 +237,7 @@ export const createConsoleManager = (
await closeInstance(id);
});
});
logger.show();
Loggers.console.show();
}

webviewPanel.title = `${instance.wingfile} - [console]`;
Expand Down Expand Up @@ -290,7 +292,9 @@ export const createConsoleManager = (
if (!instance) {
return;
}
logger.appendLine(`Closing Console instance: '${instance.wingfile}'`);
Loggers.console.appendLine(
`Closing Console instance: '${instance.wingfile}'`
);

instance.client.close();
instance.onDidClose();
Expand Down Expand Up @@ -318,5 +322,10 @@ export const createConsoleManager = (
getActiveInstanceId: () => {
return activeInstanceId;
},
stopAll: async () => {
Object.keys(instances).forEach(async (id) => {
await closeInstance(id);
});
},
};
};
Loading

0 comments on commit ca5f17a

Please sign in to comment.