Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nextls): allow extension to force a download #81

Merged
merged 8 commits into from
Mar 6, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules
.vscode-test/
*.vsix
test-bin
.elixir-tools
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
"build-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --target=node16",
"build": "yarn build-base --sourcemap",
"watch": "yarn build-base --sourcemap --watch",
"pretest": "yarn typecheck && yarn run build",
"pretest": "yarn typecheck && yarn compile-dist && yarn build",
"test": "node ./out/test/runTest.js"
},
"dependencies": {
Expand All @@ -225,4 +225,4 @@
"sinon": "^17.0.1",
"typescript": "^4.9.5"
}
}
}
10 changes: 9 additions & 1 deletion src/commands/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import * as fsp from "fs/promises";
import * as path from "path";
import * as os from "os";

let binName: string;

if (os.platform() === "win32") {
binName = "nextls.exe";
} else {
binName = "nextls";
}

export const run = async (cacheDir: string) => {
if (cacheDir[0] === "~") {
cacheDir = path.join(os.homedir(), cacheDir.slice(1));
}
const bin = path.join(cacheDir, "nextls");
const bin = path.join(cacheDir, binName);
await fsp
.rm(bin)
.then(
Expand Down
45 changes: 38 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,29 @@ async function activateCredo(
}
}

// In case the auto updater gets busted, we want the ability to force a download.
// By incremented the key here, we should be able to force a download when the extension updates.
export function forceDownload(context: vscode.ExtensionContext): boolean {
let forceDownload: boolean = context.globalState.get(
"elixir-tools-force-next-ls-download-v1"
);
channel.info(
`value of elixir-tools-force-next-ls-download-v1: ${forceDownload}`
);
if (forceDownload === undefined) {
forceDownload = true;
}

context.globalState.update("elixir-tools-force-next-ls-download-v1", false);

return forceDownload;
}

async function activateNextLS(
context: vscode.ExtensionContext,
_mixfile: vscode.Uri
) {
channel.info("activating next ls");
let config = vscode.workspace.getConfiguration("elixir-tools.nextLS");

if (config.get("enable")) {
Expand All @@ -113,12 +132,11 @@ async function activateNextLS(
switch (config.get("adapter")) {
case "stdio":
let cacheDir: string = config.get("installationDirectory")!;

if (cacheDir[0] === "~") {
cacheDir = path.join(os.homedir(), cacheDir.slice(1));
}
const command = await ensureNextLSDownloaded(cacheDir, {
force: false,
force: forceDownload(context),
});

serverOptions = {
Expand Down Expand Up @@ -181,13 +199,18 @@ async function activateNextLS(
}
}

export async function activate(context: vscode.ExtensionContext) {
export async function activate(
context: vscode.ExtensionContext
): Promise<vscode.ExtensionContext> {
let files = await vscode.workspace.findFiles("mix.exs");
channel.info(`files: ${files[0]}`);

if (files[0]) {
await activateCredo(context, files[0]);
await activateNextLS(context, files[0]);
}

return context;
}

export function deactivate() {
Expand All @@ -209,7 +232,12 @@ export async function ensureNextLSDownloaded(
cacheDir: string,
opts: { force?: boolean } = {}
): Promise<string> {
const bin = path.join(cacheDir, "nextls");
let bin: string;
if (os.platform() === "win32") {
bin = path.join(cacheDir, "nextls.exe");
} else {
bin = path.join(cacheDir, "nextls");
}

const shouldDownload = opts.force || (await isBinaryMissing(bin));

Expand All @@ -221,6 +249,7 @@ export async function ensureNextLSDownloaded(
const exe = getExe(platform);
const url = `https://github.com/elixir-tools/next-ls/releases/latest/download/${exe}`;

console.log(`Starting download from ${url}`);
channel.info(`Starting download from ${url}`);

await fetch(url).then((res) => {
Expand All @@ -232,10 +261,12 @@ export async function ensureNextLSDownloaded(
file.on("error", reject);
})
.then(() => channel.info("Downloaded NextLS!"))
.catch(() =>
channel.error("Failed to write downloaded executable to a file")
);
.catch(() => {
console.log("Failed to write downloaded executable to a file");
channel.error("Failed to write downloaded executable to a file");
});
} else {
console.log(`Failed to write download Next LS: status=${res.status}`);
channel.error(`Failed to write download Next LS: status=${res.status}`);
}
});
Expand Down
3 changes: 3 additions & 0 deletions src/test/fixtures/basic/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"elixir-tools.nextLS.installationDirectory": "./src/test/fixtures/basic/test-bin"
}
5 changes: 5 additions & 0 deletions src/test/fixtures/basic/lib/basic.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Basic do
def run do
Enum.map([:one, :two], &Function.identity/1)
end
end
32 changes: 32 additions & 0 deletions src/test/fixtures/basic/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule Basic.MixProject do
use Mix.Project

@version "0.1.0"

def project do
[
app: :basic,
description: "Basic app",
version: @version,
elixir: "~> 1.13",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger, :crypto]
]
end

defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

# Run "mix help deps" to learn about dependencies.
defp deps do
[]
end
end
10 changes: 9 additions & 1 deletion src/test/runTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ async function main() {
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "./suite/index");
const testWorkspace = path.resolve(
__dirname,
"../../src/test/fixtures/basic"
);

// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [testWorkspace],
});
} catch (err) {
console.error("Failed to run tests", err);
process.exit(1);
Expand Down
52 changes: 42 additions & 10 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as assert from "assert";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
Expand All @@ -9,20 +10,25 @@ import * as myExtension from "../../extension.js";
import * as uninstall from "../../commands/uninstall.js";
import * as sinon from "sinon";

let binName: string;

if (os.platform() === "win32") {
binName = "nextls.exe";
} else {
binName = "nextls";
}

// TODO: should extract the tests to the directory of the file under test
suite("Extension Test Suite", () => {
vscode.window.showInformationMessage("Start all tests.");
let showInformationMessage;

setup(function () {
fs.rmSync("./test-bin", { recursive: true, force: true });
showInformationMessage = sinon
.stub(vscode.window, "showInformationMessage")
.returns(
new Promise((resolve) => {
return resolve({ title: "Yes" });
})
);
showInformationMessage = sinon.stub(
vscode.window,
"showInformationMessage"
);
});

teardown(function () {
Expand All @@ -34,18 +40,18 @@ suite("Extension Test Suite", () => {
fs.mkdirSync("./test-bin", { recursive: true });

let result = await myExtension.ensureNextLSDownloaded("test-bin");
assert.equal(path.normalize(result), path.normalize("test-bin/nextls"));
assert.equal(path.normalize(result), path.normalize(`test-bin/${binName}`));
});

test("uninstalls Next LS", async function () {
fs.mkdirSync("./test-bin", { recursive: true });
fs.writeFileSync("./test-bin/nextls", "hello word");
fs.writeFileSync(`./test-bin/${binName}`, "hello word");

await uninstall.run("./test-bin");

assert.equal(
showInformationMessage.getCall(0).args[0],
`Uninstalled Next LS from ${path.normalize("test-bin/nextls")}`
`Uninstalled Next LS from ${path.normalize(`test-bin/${binName}`)}`
);
});

Expand All @@ -62,4 +68,30 @@ suite("Extension Test Suite", () => {
/due to Error: ENOENT: no such file or directory, lstat/
);
});

if (os.platform() !== "win32") {
// TODO: make a test for the opposite case. As of right now, I'm not entirely
// sure how to set globalState inside a test before the extension activates.
test("forces a download if the special key is not set", async function () {
let fixpath = path.join(__dirname, "../../../src/test/fixtures/basic");
let binpath = path.join(fixpath, "test-bin");
fs.mkdirSync(path.normalize(binpath), { recursive: true });
fs.writeFileSync(
path.normalize(path.join(binpath, binName)),
"hello world"
);
let ext = vscode.extensions.getExtension("elixir-tools.elixir-tools");

await ext.activate();

const doc = await vscode.workspace.openTextDocument(
path.join(fixpath, "mix.exs")
);
await vscode.window.showTextDocument(doc);
await new Promise((resolve) => setTimeout(resolve, 1000));

let nextls = fs.readFileSync(path.normalize(path.join(binpath, binName)));
assert.notEqual("hello world", nextls);
}).timeout(5000);
}
});
Loading