diff options
Diffstat (limited to 'editors/code/src')
-rw-r--r-- | editors/code/src/client.ts | 22 | ||||
-rw-r--r-- | editors/code/src/config.ts | 94 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 12 | ||||
-rw-r--r-- | editors/code/src/installation/download_file.ts | 44 | ||||
-rw-r--r-- | editors/code/src/installation/fetch_latest_artifact_metadata.ts | 46 | ||||
-rw-r--r-- | editors/code/src/installation/interfaces.ts | 55 | ||||
-rw-r--r-- | editors/code/src/installation/language_server.ts | 146 |
7 files changed, 393 insertions, 26 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 7e7e909dd..2e3d4aba2 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -1,24 +1,18 @@ | |||
1 | import { homedir } from 'os'; | ||
2 | import * as lc from 'vscode-languageclient'; | 1 | import * as lc from 'vscode-languageclient'; |
3 | import { spawnSync } from 'child_process'; | ||
4 | 2 | ||
5 | import { window, workspace } from 'vscode'; | 3 | import { window, workspace } from 'vscode'; |
6 | import { Config } from './config'; | 4 | import { Config } from './config'; |
5 | import { ensureLanguageServerBinary } from './installation/language_server'; | ||
7 | 6 | ||
8 | export function createClient(config: Config): lc.LanguageClient { | 7 | export async function createClient(config: Config): Promise<null | lc.LanguageClient> { |
9 | // '.' Is the fallback if no folder is open | 8 | // '.' Is the fallback if no folder is open |
10 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). | 9 | // TODO?: Workspace folders support Uri's (eg: file://test.txt). |
11 | // It might be a good idea to test if the uri points to a file. | 10 | // It might be a good idea to test if the uri points to a file. |
12 | const workspaceFolderPath = workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.'; | 11 | const workspaceFolderPath = workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.'; |
13 | 12 | ||
14 | const raLspServerPath = expandPathResolving(config.raLspServerPath); | 13 | const raLspServerPath = await ensureLanguageServerBinary(config.langServerSource); |
15 | if (spawnSync(raLspServerPath, ["--version"]).status !== 0) { | 14 | if (!raLspServerPath) return null; |
16 | window.showErrorMessage( | 15 | |
17 | `Unable to execute '${raLspServerPath} --version'\n\n` + | ||
18 | `Perhaps it is not in $PATH?\n\n` + | ||
19 | `PATH=${process.env.PATH}\n` | ||
20 | ); | ||
21 | } | ||
22 | const run: lc.Executable = { | 16 | const run: lc.Executable = { |
23 | command: raLspServerPath, | 17 | command: raLspServerPath, |
24 | options: { cwd: workspaceFolderPath }, | 18 | options: { cwd: workspaceFolderPath }, |
@@ -87,9 +81,3 @@ export function createClient(config: Config): lc.LanguageClient { | |||
87 | res.registerProposedFeatures(); | 81 | res.registerProposedFeatures(); |
88 | return res; | 82 | return res; |
89 | } | 83 | } |
90 | function expandPathResolving(path: string) { | ||
91 | if (path.startsWith('~/')) { | ||
92 | return path.replace('~', homedir()); | ||
93 | } | ||
94 | return path; | ||
95 | } | ||
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 524620433..418845436 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | import * as os from "os"; | ||
1 | import * as vscode from 'vscode'; | 2 | import * as vscode from 'vscode'; |
3 | import { BinarySource } from "./installation/interfaces"; | ||
2 | 4 | ||
3 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; | 5 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; |
4 | 6 | ||
@@ -16,10 +18,11 @@ export interface CargoFeatures { | |||
16 | } | 18 | } |
17 | 19 | ||
18 | export class Config { | 20 | export class Config { |
21 | langServerSource!: null | BinarySource; | ||
22 | |||
19 | highlightingOn = true; | 23 | highlightingOn = true; |
20 | rainbowHighlightingOn = false; | 24 | rainbowHighlightingOn = false; |
21 | enableEnhancedTyping = true; | 25 | enableEnhancedTyping = true; |
22 | raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; | ||
23 | lruCapacity: null | number = null; | 26 | lruCapacity: null | number = null; |
24 | displayInlayHints = true; | 27 | displayInlayHints = true; |
25 | maxInlayHintLength: null | number = null; | 28 | maxInlayHintLength: null | number = null; |
@@ -45,11 +48,89 @@ export class Config { | |||
45 | private prevCargoWatchOptions: null | CargoWatchOptions = null; | 48 | private prevCargoWatchOptions: null | CargoWatchOptions = null; |
46 | 49 | ||
47 | constructor(ctx: vscode.ExtensionContext) { | 50 | constructor(ctx: vscode.ExtensionContext) { |
48 | vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), null, ctx.subscriptions); | 51 | vscode.workspace.onDidChangeConfiguration(_ => this.refresh(ctx), null, ctx.subscriptions); |
49 | this.refresh(); | 52 | this.refresh(ctx); |
53 | } | ||
54 | |||
55 | private static expandPathResolving(path: string) { | ||
56 | if (path.startsWith('~/')) { | ||
57 | return path.replace('~', os.homedir()); | ||
58 | } | ||
59 | return path; | ||
50 | } | 60 | } |
51 | 61 | ||
52 | private refresh() { | 62 | /** |
63 | * Name of the binary artifact for `ra_lsp_server` that is published for | ||
64 | * `platform` on GitHub releases. (It is also stored under the same name when | ||
65 | * downloaded by the extension). | ||
66 | */ | ||
67 | private static prebuiltLangServerFileName( | ||
68 | platform: NodeJS.Platform, | ||
69 | arch: string | ||
70 | ): null | string { | ||
71 | // See possible `arch` values here: | ||
72 | // https://nodejs.org/api/process.html#process_process_arch | ||
73 | |||
74 | switch (platform) { | ||
75 | |||
76 | case "linux": { | ||
77 | switch (arch) { | ||
78 | case "arm": | ||
79 | case "arm64": return null; | ||
80 | |||
81 | default: return "ra_lsp_server-linux"; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | case "darwin": return "ra_lsp_server-mac"; | ||
86 | case "win32": return "ra_lsp_server-windows.exe"; | ||
87 | |||
88 | // Users on these platforms yet need to manually build from sources | ||
89 | case "aix": | ||
90 | case "android": | ||
91 | case "freebsd": | ||
92 | case "openbsd": | ||
93 | case "sunos": | ||
94 | case "cygwin": | ||
95 | case "netbsd": return null; | ||
96 | // The list of platforms is exhaustive (see `NodeJS.Platform` type definition) | ||
97 | } | ||
98 | } | ||
99 | |||
100 | private static langServerBinarySource( | ||
101 | ctx: vscode.ExtensionContext, | ||
102 | config: vscode.WorkspaceConfiguration | ||
103 | ): null | BinarySource { | ||
104 | const langServerPath = RA_LSP_DEBUG ?? config.get<null | string>("raLspServerPath"); | ||
105 | |||
106 | if (langServerPath) { | ||
107 | return { | ||
108 | type: BinarySource.Type.ExplicitPath, | ||
109 | path: Config.expandPathResolving(langServerPath) | ||
110 | }; | ||
111 | } | ||
112 | |||
113 | const prebuiltBinaryName = Config.prebuiltLangServerFileName( | ||
114 | process.platform, process.arch | ||
115 | ); | ||
116 | |||
117 | if (!prebuiltBinaryName) return null; | ||
118 | |||
119 | return { | ||
120 | type: BinarySource.Type.GithubRelease, | ||
121 | dir: ctx.globalStoragePath, | ||
122 | file: prebuiltBinaryName, | ||
123 | repo: { | ||
124 | name: "rust-analyzer", | ||
125 | owner: "rust-analyzer", | ||
126 | } | ||
127 | }; | ||
128 | } | ||
129 | |||
130 | |||
131 | // FIXME: revisit the logic for `if (.has(...)) config.get(...)` set default | ||
132 | // values only in one place (i.e. remove default values from non-readonly members declarations) | ||
133 | private refresh(ctx: vscode.ExtensionContext) { | ||
53 | const config = vscode.workspace.getConfiguration('rust-analyzer'); | 134 | const config = vscode.workspace.getConfiguration('rust-analyzer'); |
54 | 135 | ||
55 | let requireReloadMessage = null; | 136 | let requireReloadMessage = null; |
@@ -82,10 +163,7 @@ export class Config { | |||
82 | this.prevEnhancedTyping = this.enableEnhancedTyping; | 163 | this.prevEnhancedTyping = this.enableEnhancedTyping; |
83 | } | 164 | } |
84 | 165 | ||
85 | if (config.has('raLspServerPath')) { | 166 | this.langServerSource = Config.langServerBinarySource(ctx, config); |
86 | this.raLspServerPath = | ||
87 | RA_LSP_DEBUG || (config.get('raLspServerPath') as string); | ||
88 | } | ||
89 | 167 | ||
90 | if (config.has('cargo-watch.enable')) { | 168 | if (config.has('cargo-watch.enable')) { |
91 | this.cargoWatchOptions.enable = config.get<boolean>( | 169 | this.cargoWatchOptions.enable = config.get<boolean>( |
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index aa75943bf..70042a479 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -11,6 +11,9 @@ export class Ctx { | |||
11 | // deal with it. | 11 | // deal with it. |
12 | // | 12 | // |
13 | // Ideally, this should be replaced with async getter though. | 13 | // Ideally, this should be replaced with async getter though. |
14 | // FIXME: this actually needs syncronization of some kind (check how | ||
15 | // vscode deals with `deactivate()` call when extension has some work scheduled | ||
16 | // on the event loop to get a better picture of what we can do here) | ||
14 | client: lc.LanguageClient | null = null; | 17 | client: lc.LanguageClient | null = null; |
15 | private extCtx: vscode.ExtensionContext; | 18 | private extCtx: vscode.ExtensionContext; |
16 | private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; | 19 | private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; |
@@ -26,7 +29,14 @@ export class Ctx { | |||
26 | await old.stop(); | 29 | await old.stop(); |
27 | } | 30 | } |
28 | this.client = null; | 31 | this.client = null; |
29 | const client = createClient(this.config); | 32 | const client = await createClient(this.config); |
33 | if (!client) { | ||
34 | throw new Error( | ||
35 | "Rust Analyzer Language Server is not available. " + | ||
36 | "Please, ensure its [proper installation](https://github.com/rust-analyzer/rust-analyzer/tree/master/docs/user#vs-code)." | ||
37 | ); | ||
38 | } | ||
39 | |||
30 | this.pushCleanup(client.start()); | 40 | this.pushCleanup(client.start()); |
31 | await client.onReady(); | 41 | await client.onReady(); |
32 | 42 | ||
diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts new file mode 100644 index 000000000..f1f9f4a25 --- /dev/null +++ b/editors/code/src/installation/download_file.ts | |||
@@ -0,0 +1,44 @@ | |||
1 | import fetch from "node-fetch"; | ||
2 | import * as fs from "fs"; | ||
3 | import { strict as assert } from "assert"; | ||
4 | |||
5 | /** | ||
6 | * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. | ||
7 | * `onProgress` callback is called on recieveing each chunk of bytes | ||
8 | * to track the progress of downloading, it gets the already read and total | ||
9 | * amount of bytes to read as its parameters. | ||
10 | */ | ||
11 | export async function downloadFile( | ||
12 | url: string, | ||
13 | destFilePath: fs.PathLike, | ||
14 | destFilePermissions: number, | ||
15 | onProgress: (readBytes: number, totalBytes: number) => void | ||
16 | ): Promise<void> { | ||
17 | const res = await fetch(url); | ||
18 | |||
19 | if (!res.ok) { | ||
20 | console.log("Error", res.status, "while downloading file from", url); | ||
21 | console.dir({ body: await res.text(), headers: res.headers }, { depth: 3 }); | ||
22 | |||
23 | throw new Error(`Got response ${res.status} when trying to download a file`); | ||
24 | } | ||
25 | |||
26 | const totalBytes = Number(res.headers.get('content-length')); | ||
27 | assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); | ||
28 | |||
29 | let readBytes = 0; | ||
30 | |||
31 | console.log("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); | ||
32 | |||
33 | return new Promise<void>((resolve, reject) => res.body | ||
34 | .on("data", (chunk: Buffer) => { | ||
35 | readBytes += chunk.length; | ||
36 | onProgress(readBytes, totalBytes); | ||
37 | }) | ||
38 | .on("error", reject) | ||
39 | .pipe(fs | ||
40 | .createWriteStream(destFilePath, { mode: destFilePermissions }) | ||
41 | .on("close", resolve) | ||
42 | ) | ||
43 | ); | ||
44 | } | ||
diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_latest_artifact_metadata.ts new file mode 100644 index 000000000..7e3700603 --- /dev/null +++ b/editors/code/src/installation/fetch_latest_artifact_metadata.ts | |||
@@ -0,0 +1,46 @@ | |||
1 | import fetch from "node-fetch"; | ||
2 | import { GithubRepo, ArtifactMetadata } from "./interfaces"; | ||
3 | |||
4 | const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; | ||
5 | |||
6 | /** | ||
7 | * Fetches the latest release from GitHub `repo` and returns metadata about | ||
8 | * `artifactFileName` shipped with this release or `null` if no such artifact was published. | ||
9 | */ | ||
10 | export async function fetchLatestArtifactMetadata( | ||
11 | repo: GithubRepo, artifactFileName: string | ||
12 | ): Promise<null | ArtifactMetadata> { | ||
13 | |||
14 | const repoOwner = encodeURIComponent(repo.owner); | ||
15 | const repoName = encodeURIComponent(repo.name); | ||
16 | |||
17 | const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; | ||
18 | const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; | ||
19 | |||
20 | // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`) | ||
21 | |||
22 | console.log("Issuing request for released artifacts metadata to", requestUrl); | ||
23 | |||
24 | const response: GithubRelease = await fetch(requestUrl, { | ||
25 | headers: { Accept: "application/vnd.github.v3+json" } | ||
26 | }) | ||
27 | .then(res => res.json()); | ||
28 | |||
29 | const artifact = response.assets.find(artifact => artifact.name === artifactFileName); | ||
30 | |||
31 | if (!artifact) return null; | ||
32 | |||
33 | return { | ||
34 | releaseName: response.name, | ||
35 | downloadUrl: artifact.browser_download_url | ||
36 | }; | ||
37 | |||
38 | // We omit declaration of tremendous amount of fields that we are not using here | ||
39 | interface GithubRelease { | ||
40 | name: string; | ||
41 | assets: Array<{ | ||
42 | name: string; | ||
43 | browser_download_url: string; | ||
44 | }>; | ||
45 | } | ||
46 | } | ||
diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts new file mode 100644 index 000000000..8039d0b90 --- /dev/null +++ b/editors/code/src/installation/interfaces.ts | |||
@@ -0,0 +1,55 @@ | |||
1 | export interface GithubRepo { | ||
2 | name: string; | ||
3 | owner: string; | ||
4 | } | ||
5 | |||
6 | /** | ||
7 | * Metadata about particular artifact retrieved from GitHub releases. | ||
8 | */ | ||
9 | export interface ArtifactMetadata { | ||
10 | releaseName: string; | ||
11 | downloadUrl: string; | ||
12 | } | ||
13 | |||
14 | /** | ||
15 | * Represents the source of a binary artifact which is either specified by the user | ||
16 | * explicitly, or bundled by this extension from GitHub releases. | ||
17 | */ | ||
18 | export type BinarySource = BinarySource.ExplicitPath | BinarySource.GithubRelease; | ||
19 | |||
20 | export namespace BinarySource { | ||
21 | /** | ||
22 | * Type tag for `BinarySource` discriminated union. | ||
23 | */ | ||
24 | export const enum Type { ExplicitPath, GithubRelease } | ||
25 | |||
26 | export interface ExplicitPath { | ||
27 | type: Type.ExplicitPath; | ||
28 | |||
29 | /** | ||
30 | * Filesystem path to the binary specified by the user explicitly. | ||
31 | */ | ||
32 | path: string; | ||
33 | } | ||
34 | |||
35 | export interface GithubRelease { | ||
36 | type: Type.GithubRelease; | ||
37 | |||
38 | /** | ||
39 | * Repository where the binary is stored. | ||
40 | */ | ||
41 | repo: GithubRepo; | ||
42 | |||
43 | /** | ||
44 | * Directory on the filesystem where the bundled binary is stored. | ||
45 | */ | ||
46 | dir: string; | ||
47 | |||
48 | /** | ||
49 | * Name of the binary file. It is stored under the same name on GitHub releases | ||
50 | * and in local `.dir`. | ||
51 | */ | ||
52 | file: string; | ||
53 | } | ||
54 | |||
55 | } | ||
diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts new file mode 100644 index 000000000..52c5cbe7d --- /dev/null +++ b/editors/code/src/installation/language_server.ts | |||
@@ -0,0 +1,146 @@ | |||
1 | import * as vscode from "vscode"; | ||
2 | import * as path from "path"; | ||
3 | import { strict as assert } from "assert"; | ||
4 | import { promises as fs } from "fs"; | ||
5 | import { promises as dns } from "dns"; | ||
6 | import { spawnSync } from "child_process"; | ||
7 | import { throttle } from "throttle-debounce"; | ||
8 | |||
9 | import { BinarySource } from "./interfaces"; | ||
10 | import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata"; | ||
11 | import { downloadFile } from "./download_file"; | ||
12 | |||
13 | export async function downloadLatestLanguageServer( | ||
14 | {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease | ||
15 | ) { | ||
16 | const { releaseName, downloadUrl } = (await fetchLatestArtifactMetadata( | ||
17 | repo, artifactFileName | ||
18 | ))!; | ||
19 | |||
20 | await fs.mkdir(installationDir).catch(err => assert.strictEqual( | ||
21 | err?.code, | ||
22 | "EEXIST", | ||
23 | `Couldn't create directory "${installationDir}" to download `+ | ||
24 | `language server binary: ${err.message}` | ||
25 | )); | ||
26 | |||
27 | const installationPath = path.join(installationDir, artifactFileName); | ||
28 | |||
29 | console.time("Downloading ra_lsp_server"); | ||
30 | await vscode.window.withProgress( | ||
31 | { | ||
32 | location: vscode.ProgressLocation.Notification, | ||
33 | cancellable: false, // FIXME: add support for canceling download? | ||
34 | title: `Downloading language server (${releaseName})` | ||
35 | }, | ||
36 | async (progress, _cancellationToken) => { | ||
37 | let lastPrecentage = 0; | ||
38 | const filePermissions = 0o755; // (rwx, r_x, r_x) | ||
39 | await downloadFile(downloadUrl, installationPath, filePermissions, throttle( | ||
40 | 200, | ||
41 | /* noTrailing: */ true, | ||
42 | (readBytes, totalBytes) => { | ||
43 | const newPercentage = (readBytes / totalBytes) * 100; | ||
44 | progress.report({ | ||
45 | message: newPercentage.toFixed(0) + "%", | ||
46 | increment: newPercentage - lastPrecentage | ||
47 | }); | ||
48 | |||
49 | lastPrecentage = newPercentage; | ||
50 | }) | ||
51 | ); | ||
52 | } | ||
53 | ); | ||
54 | console.timeEnd("Downloading ra_lsp_server"); | ||
55 | } | ||
56 | export async function ensureLanguageServerBinary( | ||
57 | langServerSource: null | BinarySource | ||
58 | ): Promise<null | string> { | ||
59 | |||
60 | if (!langServerSource) { | ||
61 | vscode.window.showErrorMessage( | ||
62 | "Unfortunately we don't ship binaries for your platform yet. " + | ||
63 | "You need to manually clone rust-analyzer repository and " + | ||
64 | "run `cargo xtask install --server` to build the language server from sources. " + | ||
65 | "If you feel that your platform should be supported, please create an issue " + | ||
66 | "about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " + | ||
67 | "will consider it." | ||
68 | ); | ||
69 | return null; | ||
70 | } | ||
71 | |||
72 | switch (langServerSource.type) { | ||
73 | case BinarySource.Type.ExplicitPath: { | ||
74 | if (isBinaryAvailable(langServerSource.path)) { | ||
75 | return langServerSource.path; | ||
76 | } | ||
77 | |||
78 | vscode.window.showErrorMessage( | ||
79 | `Unable to run ${langServerSource.path} binary. ` + | ||
80 | `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + | ||
81 | "value to `null` or remove it from the settings to use it by default." | ||
82 | ); | ||
83 | return null; | ||
84 | } | ||
85 | case BinarySource.Type.GithubRelease: { | ||
86 | const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file); | ||
87 | |||
88 | if (isBinaryAvailable(prebuiltBinaryPath)) { | ||
89 | return prebuiltBinaryPath; | ||
90 | } | ||
91 | |||
92 | const userResponse = await vscode.window.showInformationMessage( | ||
93 | "Language server binary for rust-analyzer was not found. " + | ||
94 | "Do you want to download it now?", | ||
95 | "Download now", "Cancel" | ||
96 | ); | ||
97 | if (userResponse !== "Download now") return null; | ||
98 | |||
99 | try { | ||
100 | await downloadLatestLanguageServer(langServerSource); | ||
101 | } catch (err) { | ||
102 | vscode.window.showErrorMessage( | ||
103 | `Failed to download language server from ${langServerSource.repo.name} ` + | ||
104 | `GitHub repository: ${err.message}` | ||
105 | ); | ||
106 | |||
107 | dns.resolve('example.com').then( | ||
108 | addrs => console.log("DNS resolution for example.com was successful", addrs), | ||
109 | err => { | ||
110 | console.error( | ||
111 | "DNS resolution for example.com failed, " + | ||
112 | "there might be an issue with Internet availability" | ||
113 | ); | ||
114 | console.error(err); | ||
115 | } | ||
116 | ); | ||
117 | |||
118 | return null; | ||
119 | } | ||
120 | |||
121 | if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, | ||
122 | `Downloaded language server binary is not functional.` + | ||
123 | `Downloaded from: ${JSON.stringify(langServerSource)}` | ||
124 | ); | ||
125 | |||
126 | |||
127 | vscode.window.showInformationMessage( | ||
128 | "Rust analyzer language server was successfully installed 🦀" | ||
129 | ); | ||
130 | |||
131 | return prebuiltBinaryPath; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | function isBinaryAvailable(binaryPath: string) { | ||
136 | const res = spawnSync(binaryPath, ["--version"]); | ||
137 | |||
138 | // ACHTUNG! `res` type declaration is inherently wrong, see | ||
139 | // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 | ||
140 | |||
141 | console.log("Checked binary availablity via --version", res); | ||
142 | console.log(binaryPath, "--version output:", res.output?.map(String)); | ||
143 | |||
144 | return res.status === 0; | ||
145 | } | ||
146 | } | ||