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

Overhaul extension bundling #844

Merged
merged 5 commits into from
Dec 30, 2023
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
6 changes: 6 additions & 0 deletions stylua-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ To view the changelog of the StyLua binary, see [here](https://github.com/Johnny

### Added

- The extension now supports using a StyLua binary found on the PATH
- This can be configured via setting `stylua.searchBinaryOnPATH`
- If the binary fails to execute, we fall back to the bundled version
- Added configuration option `stylua.configPath` to provide a direct path to a `stylua.toml` file. Note: this will override any workspace config lookup
- Added configuration option `stylua.verify` to pass `--verify` to StyLua CLI when formatting a file. This enforces output verification
- Added command `StyLua: Select Version` to customize which version of StyLua to install. This command updates the `stylua.targetReleaseVersion` setting
Expand All @@ -27,6 +30,9 @@ To view the changelog of the StyLua binary, see [here](https://github.com/Johnny

- Removed excessive error notifications on formatting failure and replaced with VSCode language status bar item
- `.styluaignore` is now registered as an ignore file with an appropriate file icon
- StyLua version updates will now be shown on the status bar. To disable these notifications, configure `stylua.disableVersionCheck`
- If `stylua.targetReleaseVersion` is set, we will now still notify about the latest release version
- If `stylua.targetReleaseVersion` is set, but the installed version does not match, prompt to install desired version

## [1.5.0] - 2023-03-11

Expand Down
25 changes: 25 additions & 0 deletions stylua-vscode/package-lock.json

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

7 changes: 7 additions & 0 deletions stylua-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@
"default": null,
"description": "Specifies the path of StyLua. If not specified, will automatically download one from the GitHub releases."
},
"stylua.searchBinaryInPATH": {
"type": "boolean",
"default": true,
"markdownDescription": "Search for the StyLua binary in the `PATH` environment variable, and use this if available. If disabled, falls back to a bundled binary"
},
"stylua.disableVersionCheck": {
"type": "boolean",
"default": false,
Expand Down Expand Up @@ -143,6 +148,7 @@
"@types/node-fetch": "^2.5.8",
"@types/unzipper": "^0.10.8",
"@types/vscode": "^1.82.0",
"@types/which": "^3.0.3",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"@vscode/test-cli": "^0.0.4",
Expand All @@ -156,6 +162,7 @@
"dependencies": {
"ignore": "^5.1.8",
"node-fetch": "^2.6.1",
"node-which": "^1.0.0",
"semver": "^7.5.4",
"unzipper": "^0.10.14"
}
Expand Down
186 changes: 127 additions & 59 deletions stylua-vscode/src/download.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import * as vscode from "vscode";
import * as unzip from "unzipper";
import * as util from "./util";
import * as semver from "semver";
import fetch from "node-fetch";
import { createWriteStream } from "fs";
import { executeStylua } from "./stylua";
import { GitHub, GitHubRelease } from "./github";
import which = require("which");

interface StyluaInfo {
export enum ResolveMode {
configuration = "configuration",
path = "PATH",
bundled = "bundled",
}

export interface StyluaInfo {
path: string;
version: string | undefined;
resolveMode: ResolveMode;
version?: string | undefined;
}

const getStyluaVersion = async (path: string, cwd?: string) => {
Expand All @@ -22,52 +31,100 @@ const getStyluaVersion = async (path: string, cwd?: string) => {
}
};

export class StyluaDownloader {
export class StyluaDownloader implements vscode.Disposable {
statusBarUpdateItem = vscode.window.createStatusBarItem(
"stylua.installUpdate",
vscode.StatusBarAlignment.Right
);

constructor(
private readonly storageDirectory: vscode.Uri,
private readonly github: GitHub
private readonly github: GitHub,
private readonly outputChannel: vscode.LogOutputChannel
) {}

public async findStylua(cwd?: string): Promise<StyluaInfo> {
// 1) If `stylua.styluaPath` has been specified, use that directly
const settingPath = vscode.workspace
.getConfiguration("stylua")
.get<string | null>("styluaPath");
if (settingPath) {
this.outputChannel.info(
`Stylua path explicitly configured: ${settingPath}`
);
return { path: settingPath, resolveMode: ResolveMode.configuration };
}

// 2) Find a `stylua` binary available on PATH
if (
vscode.workspace
.getConfiguration("stylua")
.get<boolean>("searchBinaryInPATH")
) {
this.outputChannel.info("Searching for stylua on PATH");
const resolvedPath = await which("stylua", { nothrow: true });
if (resolvedPath) {
this.outputChannel.info(`Stylua found on PATH: ${resolvedPath}`);
if (await getStyluaVersion(resolvedPath, cwd)) {
return { path: resolvedPath, resolveMode: ResolveMode.path };
} else {
this.outputChannel.error(
"Stylua binary found on PATH failed to execute"
);
}
}
}

// 3) Fallback to bundled stylua version
this.outputChannel.info("Falling back to bundled StyLua version");
const downloadPath = vscode.Uri.joinPath(
this.storageDirectory,
util.getDownloadOutputFilename()
);
return { path: downloadPath.fsPath, resolveMode: ResolveMode.bundled };
}

public async ensureStyluaExists(
cwd?: string
): Promise<StyluaInfo | undefined> {
const path = await this.getStyluaPath();

if (path === undefined) {
await vscode.workspace.fs.createDirectory(this.storageDirectory);
await this.downloadStyLuaVisual(util.getDesiredVersion());
const path = await this.getStyluaPath();
if (path) {
return {
path,
version: await getStyluaVersion(path),
};
} else {
return;
const stylua = await this.findStylua(cwd);

if (stylua.resolveMode === ResolveMode.bundled) {
if (!(await util.fileExists(stylua.path))) {
await vscode.workspace.fs.createDirectory(this.storageDirectory);
await this.downloadStyLuaVisual(util.getDesiredVersion());
}
} else {
if (!(await util.fileExists(path))) {
vscode.window.showErrorMessage(
`The path given for StyLua (${path}) does not exist`
);
return;
stylua.version = await getStyluaVersion(stylua.path, cwd);

// Check bundled version matches requested version
const desiredVersion = util.getDesiredVersion();
if (stylua.version && desiredVersion !== "latest") {
const desiredVersionSemver = semver.coerce(desiredVersion);
const styluaVersionSemver = semver.parse(stylua.version);
if (
desiredVersionSemver &&
styluaVersionSemver &&
semver.neq(desiredVersionSemver, styluaVersionSemver)
) {
this.openIncorrectVersionPrompt(stylua.version, desiredVersion);
}
}

const currentVersion = await getStyluaVersion(path);

// Check for latest version
if (
!vscode.workspace.getConfiguration("stylua").get("disableVersionCheck")
) {
try {
const desiredVersion = util.getDesiredVersion();
const release = await this.github.getRelease(desiredVersion);
const latestRelease = await this.github.getRelease("latest");
if (
currentVersion !==
(release.tagName.startsWith("v")
? release.tagName.substr(1)
: release.tagName)
stylua.version !==
(latestRelease.tagName.startsWith("v")
? latestRelease.tagName.substr(1)
: latestRelease.tagName)
) {
this.openUpdatePrompt(release);
this.showUpdateAvailable(latestRelease);
} else {
this.statusBarUpdateItem.hide();
}
} catch (err) {
vscode.window.showWarningMessage(
Expand All @@ -86,10 +143,22 @@ export class StyluaDownloader {
}
}
}
} else {
this.statusBarUpdateItem.hide();
}

return { path, version: currentVersion };
} else if (stylua.resolveMode === ResolveMode.configuration) {
if (!(await util.fileExists(stylua.path))) {
vscode.window.showErrorMessage(
`The path given for StyLua (${stylua.path}) does not exist`
);
return;
}
stylua.version = await getStyluaVersion(stylua.path, cwd);
} else if (stylua.resolveMode === ResolveMode.path) {
stylua.version = await getStyluaVersion(stylua.path, cwd);
}

return stylua;
}

public downloadStyLuaVisual(version: string): Thenable<void> {
Expand Down Expand Up @@ -140,41 +209,40 @@ export class StyluaDownloader {
}
}

private openUpdatePrompt(release: GitHubRelease) {
private showUpdateAvailable(release: GitHubRelease) {
this.statusBarUpdateItem.name = "StyLua Update";
this.statusBarUpdateItem.text = `StyLua update available (${release.tagName}) $(cloud-download)`;
this.statusBarUpdateItem.tooltip = "Click to update StyLua";
this.statusBarUpdateItem.command = {
title: "Update StyLua",
command: "stylua.installUpdate",
arguments: [release],
};
this.statusBarUpdateItem.backgroundColor = new vscode.ThemeColor(
"statusBarItem.warningBackground"
);
this.statusBarUpdateItem.show();
}

private openIncorrectVersionPrompt(
currentVersion: string,
requestedVersion: string
) {
vscode.window
.showInformationMessage(
`StyLua ${release.tagName} is available to install.`,
"Install",
"Later",
"Release Notes"
`The currently installed version of StyLua (${currentVersion}) does not match the requested version (${requestedVersion})`,
"Install"
)
.then((option) => {
switch (option) {
case "Install":
this.downloadStyLuaVisual(release.tagName);
break;
case "Release Notes":
vscode.env.openExternal(vscode.Uri.parse(release.htmlUrl));
this.openUpdatePrompt(release);
vscode.commands.executeCommand("stylua.reinstall");
break;
}
});
}

public async getStyluaPath(): Promise<string | undefined> {
const settingPath = vscode.workspace
.getConfiguration("stylua")
.get<string | null>("styluaPath");
if (settingPath) {
return settingPath;
}

const downloadPath = vscode.Uri.joinPath(
this.storageDirectory,
util.getDownloadOutputFilename()
);
if (await util.fileExists(downloadPath)) {
return downloadPath.fsPath;
}
dispose() {
this.statusBarUpdateItem.dispose();
}
}
Loading