From 4b266f892ad6d31d3766138cde5adb1d212cb55a Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 12:24:16 -0700 Subject: [PATCH 01/16] Refactor CLI version selection --- .../src/flow-cli/binary-versions-provider.ts | 141 ++++++++++++++ extension/src/flow-cli/cli-provider.ts | 172 ++++-------------- .../src/flow-cli/cli-selection-provider.ts | 21 ++- extension/src/server/language-server.ts | 2 +- package.json | 4 +- 5 files changed, 186 insertions(+), 154 deletions(-) create mode 100644 extension/src/flow-cli/binary-versions-provider.ts diff --git a/extension/src/flow-cli/binary-versions-provider.ts b/extension/src/flow-cli/binary-versions-provider.ts new file mode 100644 index 00000000..9ca6df8f --- /dev/null +++ b/extension/src/flow-cli/binary-versions-provider.ts @@ -0,0 +1,141 @@ +import * as semver from 'semver' +import { StateCache } from '../utils/state-cache' +import { execDefault } from '../utils/shell/exec' +import { Observable, distinctUntilChanged } from 'rxjs' +import { isEqual } from 'lodash' + +const CHECK_FLOW_CLI_CMD = (flowCommand: string): string => `${flowCommand} version --output=json` +const CHECK_FLOW_CLI_CMD_NO_JSON = (flowCommand: string): string => `${flowCommand} version` + +const KNOWN_BINS = ['flow', 'flow-c1'] + +const LEGACY_VERSION_REGEXP = /Version:\s*(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(\s|$)/m + +export interface CliBinary { + path: string + version: semver.SemVer +} + +interface FlowVersionOutput { + version: string +} + +export class BinaryVersionsProvider { + #rootCache: StateCache + #caches: { [key: string]: StateCache } = {} + + constructor (seedBinaries: string[] = []) { + // Seed the caches with the known binaries + KNOWN_BINS.forEach((bin) => { + this.add(bin) + }) + + // Seed the caches with any additional binaries + seedBinaries.forEach((bin) => { + this.add(bin) + }) + + // Create the root cache. This cache will hold all the binary information + // and is a combination of all the individual caches for each binary + this.#rootCache = new StateCache(async () => { + const binaries = await Promise.all( + Object.keys(this.#caches).map(async (bin) => { + return await this.#caches[bin].getValue().catch(() => null) + }) + ) + + // Filter out missing binaries + return binaries.filter((bin) => bin != null) as CliBinary[] + }) + } + + add (path: string): void { + if (this.#caches[path] != null) return + this.#caches[path] = new StateCache(async () => await this.#fetchBinaryInformation(path)) + this.#caches[path].subscribe(() => { + this.#rootCache?.invalidate() + }) + this.#rootCache?.invalidate() + } + + remove (path: string): void { + // Known binaries cannot be removed + if (this.#caches[path] == null || KNOWN_BINS.includes(path)) return + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.#caches[path] + this.#rootCache?.invalidate() + } + + get (name: string): StateCache | null { + return this.#caches[name] ?? null + } + + // Fetches the binary information for the given binary + async #fetchBinaryInformation (bin: string): Promise { + try { + // Get user's version informaton + const buffer: string = (await execDefault(CHECK_FLOW_CLI_CMD( + bin + ))).stdout + + // Format version string from output + const versionInfo: FlowVersionOutput = JSON.parse(buffer) + + // Ensure user has a compatible version number installed + const version: semver.SemVer | null = semver.parse(versionInfo.version) + if (version === null) return null + + return { path: bin, version } + } catch { + // Fallback to old method if JSON is not supported/fails + return await this.#fetchBinaryInformationOld(bin) + } + } + + // Old version of fetchBinaryInformation (before JSON was supported) + // Used as fallback for old CLI versions + async #fetchBinaryInformationOld (bin: string): Promise { + try { + // Get user's version informaton + const output = (await execDefault(CHECK_FLOW_CLI_CMD_NO_JSON( + bin + ))) + + let versionStr: string | null = parseFlowCliVersion(output.stdout) + if (versionStr === null) { + // Try to fallback to stderr as patch for bugged version + versionStr = parseFlowCliVersion(output.stderr) + } + + versionStr = versionStr != null ? semver.clean(versionStr) : null + if (versionStr === null) return null + + // Ensure user has a compatible version number installed + const version: semver.SemVer | null = semver.parse(versionStr) + if (version === null) return null + + return { path: bin, version } + } catch { + return null + } + } + + refresh (): void { + Object.keys(this.#caches).forEach((bin) => { + this.#caches[bin].invalidate() + }) + this.#rootCache.invalidate() + } + + async getVersions (): Promise { + return await this.#rootCache.getValue() + } + + get versions$ (): Observable { + return this.#rootCache.pipe(distinctUntilChanged(isEqual)) + } +} + +export function parseFlowCliVersion (buffer: Buffer | string): string | null { + return buffer.toString().match(LEGACY_VERSION_REGEXP)?.[1] ?? null +} diff --git a/extension/src/flow-cli/cli-provider.ts b/extension/src/flow-cli/cli-provider.ts index a5b2181a..1a4ce76f 100644 --- a/extension/src/flow-cli/cli-provider.ts +++ b/extension/src/flow-cli/cli-provider.ts @@ -1,66 +1,36 @@ import { BehaviorSubject, Observable, distinctUntilChanged, pairwise, startWith } from 'rxjs' -import { execDefault } from '../utils/shell/exec' import { StateCache } from '../utils/state-cache' -import * as semver from 'semver' import * as vscode from 'vscode' import { Settings } from '../settings/settings' import { isEqual } from 'lodash' - -const CHECK_FLOW_CLI_CMD = (flowCommand: string): string => `${flowCommand} version --output=json` -const CHECK_FLOW_CLI_CMD_NO_JSON = (flowCommand: string): string => `${flowCommand} version` - -const KNOWN_BINS = ['flow', 'flow-c1'] - -const CADENCE_V1_CLI_REGEX = /-cadence-v1.0.0/g -const LEGACY_VERSION_REGEXP = /Version:\s*(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(\s|$)/m - -export interface CliBinary { - name: string - version: semver.SemVer -} - -interface FlowVersionOutput { - version: string -} - -interface AvailableBinariesCache { - [key: string]: StateCache -} +import { CliBinary, BinaryVersionsProvider } from './binary-versions-provider' export class CliProvider { #selectedBinaryName: BehaviorSubject #currentBinary$: StateCache - #availableBinaries: AvailableBinariesCache = {} - #availableBinaries$: StateCache + #binaryVersions: BinaryVersionsProvider #settings: Settings constructor (settings: Settings) { + const initialBinaryPath = settings.getSettings().flowCommand + this.#settings = settings + this.#binaryVersions = new BinaryVersionsProvider([initialBinaryPath]) + this.#selectedBinaryName = new BehaviorSubject(initialBinaryPath) + this.#currentBinary$ = new StateCache(async () => { + const name: string = this.#selectedBinaryName.getValue() + const versionCache = this.#binaryVersions.get(name) + if (versionCache == null) return null + return await versionCache.getValue() + }) - this.#selectedBinaryName = new BehaviorSubject(settings.getSettings().flowCommand) + // Bind the selected binary to the settings this.#settings.watch$(config => config.flowCommand).subscribe((flowCommand) => { this.#selectedBinaryName.next(flowCommand) }) - this.#availableBinaries = KNOWN_BINS.reduce((acc, bin) => { - acc[bin] = new StateCache(async () => await this.#fetchBinaryInformation(bin)) - acc[bin].subscribe(() => { - this.#availableBinaries$.invalidate() - }) - return acc - }, {}) - - this.#availableBinaries$ = new StateCache(async () => { - return await this.getAvailableBinaries() - }) - - this.#currentBinary$ = new StateCache(async () => { - const name: string = this.#selectedBinaryName.getValue() - return await this.#availableBinaries[name].getValue() - }) - // Display warning to user if binary doesn't exist (only if not using the default binary) - this.#currentBinary$.subscribe((binary) => { + this.currentBinary$.subscribe((binary) => { if (binary === null && this.#selectedBinaryName.getValue() !== 'flow') { void vscode.window.showErrorMessage(`The configured Flow CLI binary "${this.#selectedBinaryName.getValue()}" does not exist. Please check your settings.`) } @@ -72,119 +42,39 @@ export class CliProvider { #watchForBinaryChanges (): void { // Subscribe to changes in the selected binary to update the caches this.#selectedBinaryName.pipe(distinctUntilChanged(), startWith(null), pairwise()).subscribe(([prev, curr]) => { - // Swap out the cache for the selected binary - if (prev != null && !KNOWN_BINS.includes(prev)) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete this.#availableBinaries[prev] - } - if (curr != null && !KNOWN_BINS.includes(curr)) { - this.#availableBinaries[curr] = new StateCache(async () => await this.#fetchBinaryInformation(curr)) - this.#availableBinaries[curr].subscribe(() => { - this.#availableBinaries$.invalidate() - }) - } + // Remove the previous binary from the cache + if (prev != null) this.#binaryVersions.remove(prev) + + // Add the current binary to the cache + if (curr != null) this.#binaryVersions.add(curr) // Invalidate the current binary cache this.#currentBinary$.invalidate() - - // Invalidate the available binaries cache - this.#availableBinaries$.invalidate() }) } - // Fetches the binary information for the given binary - async #fetchBinaryInformation (bin: string): Promise { - try { - // Get user's version informaton - const buffer: string = (await execDefault(CHECK_FLOW_CLI_CMD( - bin - ))).stdout - - // Format version string from output - const versionInfo: FlowVersionOutput = JSON.parse(buffer) - - // Ensure user has a compatible version number installed - const version: semver.SemVer | null = semver.parse(versionInfo.version) - if (version === null) return null - - return { name: bin, version } - } catch { - // Fallback to old method if JSON is not supported/fails - return await this.#fetchBinaryInformationOld(bin) - } - } - - // Old version of fetchBinaryInformation (before JSON was supported) - // Used as fallback for old CLI versions - async #fetchBinaryInformationOld (bin: string): Promise { - try { - // Get user's version informaton - const output = (await execDefault(CHECK_FLOW_CLI_CMD_NO_JSON( - bin - ))) - - let versionStr: string | null = parseFlowCliVersion(output.stdout) - if (versionStr === null) { - // Try to fallback to stderr as patch for bugged version - versionStr = parseFlowCliVersion(output.stderr) - } - - versionStr = versionStr != null ? semver.clean(versionStr) : null - if (versionStr === null) return null - - // Ensure user has a compatible version number installed - const version: semver.SemVer | null = semver.parse(versionStr) - if (version === null) return null - - return { name: bin, version } - } catch { - return null - } - } - - refresh (): void { - for (const bin in this.#availableBinaries) { - this.#availableBinaries[bin].invalidate() - } - this.#currentBinary$.invalidate() - } - - get availableBinaries$ (): Observable { - return new Observable((subscriber) => { - this.#availableBinaries$.subscribe((binaries) => { - subscriber.next(binaries) - }) - }).pipe(distinctUntilChanged(isEqual)) + async getCurrentBinary (): Promise { + return await this.#currentBinary$.getValue() } - async getAvailableBinaries (): Promise { - const bins: CliBinary[] = [] - for (const name in this.#availableBinaries) { - const binary = await this.#availableBinaries[name].getValue().catch(() => null) - if (binary !== null) { - bins.push(binary) - } - } - return bins + async setCurrentBinary (name: string): Promise { + await this.#settings.updateSettings({ flowCommand: name }) } get currentBinary$ (): Observable { return this.#currentBinary$.pipe(distinctUntilChanged(isEqual)) } - async getCurrentBinary (): Promise { - return await this.#currentBinary$.getValue() + async getBinaryVersions (): Promise { + return await this.#binaryVersions.getVersions() } - async setCurrentBinary (name: string): Promise { - await this.#settings.updateSettings({ flowCommand: name }) + get binaryVersions$ (): Observable { + return this.#binaryVersions.versions$.pipe(distinctUntilChanged(isEqual)) } -} - -export function isCadenceV1Cli (version: semver.SemVer): boolean { - return CADENCE_V1_CLI_REGEX.test(version.raw) -} -export function parseFlowCliVersion (buffer: Buffer | string): string | null { - return buffer.toString().match(LEGACY_VERSION_REGEXP)?.[1] ?? null + // Refresh all cached binary versions + refresh (): void { + this.#binaryVersions.refresh() + } } diff --git a/extension/src/flow-cli/cli-selection-provider.ts b/extension/src/flow-cli/cli-selection-provider.ts index 53637926..16486d50 100644 --- a/extension/src/flow-cli/cli-selection-provider.ts +++ b/extension/src/flow-cli/cli-selection-provider.ts @@ -1,9 +1,10 @@ +import * as vscode from 'vscode' import { zip } from 'rxjs' -import { CliBinary, CliProvider } from './cli-provider' +import { CliProvider } from './cli-provider' import { SemVer } from 'semver' -import * as vscode from 'vscode' +import { CliBinary } from './binary-versions-provider' -const CHANGE_CADENCE_VERSION = 'cadence.changeCadenceVersion' +const CHANGE_CLI_BINARY = 'cadence.changeFlowCliBinary' const CADENCE_V1_CLI_REGEX = /-cadence-v1.0.0/g // label with icon const GET_BINARY_LABEL = (version: SemVer): string => `Flow CLI v${version.format()}` @@ -19,13 +20,13 @@ export class CliSelectionProvider { this.#cliProvider = cliProvider // Register the command to toggle the version - this.#disposables.push(vscode.commands.registerCommand(CHANGE_CADENCE_VERSION, async () => { + this.#disposables.push(vscode.commands.registerCommand(CHANGE_CLI_BINARY, async () => { this.#cliProvider.refresh() await this.#toggleSelector(true) })) // Register UI components - zip(this.#cliProvider.currentBinary$, this.#cliProvider.availableBinaries$).subscribe(() => { + zip(this.#cliProvider.currentBinary$, this.#cliProvider.binaryVersions$).subscribe(() => { void this.#refreshSelector() }) this.#cliProvider.currentBinary$.subscribe((binary) => { @@ -37,7 +38,7 @@ export class CliSelectionProvider { #createStatusBarItem (version: SemVer | null): vscode.StatusBarItem { const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1) - statusBarItem.command = CHANGE_CADENCE_VERSION + statusBarItem.command = CHANGE_CLI_BINARY statusBarItem.color = new vscode.ThemeColor('statusBar.foreground') statusBarItem.tooltip = 'Click to change the Flow CLI version' @@ -85,7 +86,7 @@ export class CliSelectionProvider { // Select the current binary if (currentBinary !== null) { - const currentBinaryItem = versionSelector.items.find(item => item instanceof AvailableBinaryItem && item.path === currentBinary.name) + const currentBinaryItem = versionSelector.items.find(item => item instanceof AvailableBinaryItem && item.path === currentBinary.path) if (currentBinaryItem != null) { versionSelector.selectedItems = [currentBinaryItem] } @@ -103,7 +104,7 @@ export class CliSelectionProvider { if (this.#showSelector) { this.#versionSelector?.dispose() const currentBinary = await this.#cliProvider.getCurrentBinary() - const availableBinaries = await this.#cliProvider.getAvailableBinaries() + const availableBinaries = await this.#cliProvider.getBinaryVersions() this.#versionSelector = this.#createVersionSelector(currentBinary, availableBinaries) this.#disposables.push(this.#versionSelector) this.#versionSelector.show() @@ -134,11 +135,11 @@ class AvailableBinaryItem implements vscode.QuickPickItem { } get description (): string { - return `(${this.#binary.name})` + return `(${this.#binary.path})` } get path (): string { - return this.#binary.name + return this.#binary.path } } diff --git a/extension/src/server/language-server.ts b/extension/src/server/language-server.ts index 1973cd34..5ec3b245 100644 --- a/extension/src/server/language-server.ts +++ b/extension/src/server/language-server.ts @@ -75,7 +75,7 @@ export class LanguageServerAPI { const accessCheckMode: string = this.#settings.getSettings().accessCheckMode const configPath: string | null = this.#config.configPath - const binaryPath = (await this.#cliProvider.getCurrentBinary())?.name + const binaryPath = (await this.#cliProvider.getCurrentBinary())?.path if (binaryPath == null) { throw new Error('No flow binary found') } diff --git a/package.json b/package.json index be0e51c5..714ef41c 100644 --- a/package.json +++ b/package.json @@ -100,9 +100,9 @@ "title": "Check Dependencies" }, { - "command": "cadence.changeCadenceVersion", + "command": "cadence.changeFlowCliBinary", "category": "Cadence", - "title": "Change Cadence Version" + "title": "Change Flow CLI Binary" } ], "configuration": { From 59bab00373bef2030d499177a7c772cfce94a0d5 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 14:22:14 -0700 Subject: [PATCH 02/16] hoist current binary --- extension/src/flow-cli/cli-selection-provider.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extension/src/flow-cli/cli-selection-provider.ts b/extension/src/flow-cli/cli-selection-provider.ts index 16486d50..eb2364ee 100644 --- a/extension/src/flow-cli/cli-selection-provider.ts +++ b/extension/src/flow-cli/cli-selection-provider.ts @@ -82,16 +82,16 @@ export class CliSelectionProvider { // Update available versions const items: Array = availableBinaries.map(binary => new AvailableBinaryItem(binary)) items.push(new CustomBinaryItem()) - versionSelector.items = items - // Select the current binary - if (currentBinary !== null) { - const currentBinaryItem = versionSelector.items.find(item => item instanceof AvailableBinaryItem && item.path === currentBinary.path) - if (currentBinaryItem != null) { - versionSelector.selectedItems = [currentBinaryItem] - } + // Hoist the current binary to the top of the list + const currentBinaryIndex = items.findIndex(item => item instanceof AvailableBinaryItem && item.path === currentBinary?.path) + if (currentBinaryIndex != null) { + const currentBinaryItem = items[currentBinaryIndex] + items.splice(currentBinaryIndex, 1) + items.unshift(currentBinaryItem) } + versionSelector.items = items return versionSelector } From 38075b073d9c12b443312fbb0278b7db8b60beb3 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 14:25:13 -0700 Subject: [PATCH 03/16] fix lint --- .../src/dependency-installer/installers/flow-cli-installer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/dependency-installer/installers/flow-cli-installer.ts b/extension/src/dependency-installer/installers/flow-cli-installer.ts index 32d4c951..e4a56dde 100644 --- a/extension/src/dependency-installer/installers/flow-cli-installer.ts +++ b/extension/src/dependency-installer/installers/flow-cli-installer.ts @@ -100,7 +100,7 @@ export class InstallFlowCLI extends Installer { async checkVersion (vsn?: semver.SemVer): Promise { // Get user's version informaton this.#context.cliProvider.refresh() - const version = vsn ?? await this.#context.cliProvider.getAvailableBinaries().then(x => x.find(y => y.name === 'flow')?.version) + const version = vsn ?? await this.#context.cliProvider.getBinaryVersions().then(x => x.find(y => y.path === 'flow')?.version) if (version == null) return false if (!semver.satisfies(version, COMPATIBLE_FLOW_CLI_VERSIONS, { @@ -128,7 +128,7 @@ export class InstallFlowCLI extends Installer { async verifyInstall (): Promise { // Check if flow version is valid to verify install this.#context.cliProvider.refresh() - const version = await this.#context.cliProvider.getAvailableBinaries().then(x => x.find(y => y.name === 'flow')?.version) + const version = await this.#context.cliProvider.getBinaryVersions().then(x => x.find(y => y.path === 'flow')?.version) if (version == null) return false // Check flow-cli version number From 20b07d94073eca4d1dde3e112780241030d10305 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 14:28:31 -0700 Subject: [PATCH 04/16] fix tests --- extension/test/integration/1 - language-server.test.ts | 6 +++--- extension/test/unit/parser.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extension/test/integration/1 - language-server.test.ts b/extension/test/integration/1 - language-server.test.ts index 3fc9f30b..c1f51a86 100644 --- a/extension/test/integration/1 - language-server.test.ts +++ b/extension/test/integration/1 - language-server.test.ts @@ -8,8 +8,8 @@ import { MaxTimeout } from '../globals' import { BehaviorSubject, Subject } from 'rxjs' import { State } from 'vscode-languageclient' import * as sinon from 'sinon' -import { CliBinary } from '../../src/flow-cli/cli-provider' import { SemVer } from 'semver' +import { CliBinary } from '../../src/flow-cli/binary-versions-provider' suite('Language Server & Emulator Integration', () => { let LS: LanguageServerAPI @@ -33,7 +33,7 @@ suite('Language Server & Emulator Integration', () => { // create a mock cli provider without invokign the constructor cliBinary$ = new BehaviorSubject({ - name: 'flow', + path: 'flow', version: new SemVer('1.0.0') }) const mockCliProvider = { @@ -63,7 +63,7 @@ suite('Language Server & Emulator Integration', () => { fileModified$.next() pathChanged$.next('foo') cliBinary$.next({ - name: 'flow', + path: 'flow', version: new SemVer('1.0.1') }) diff --git a/extension/test/unit/parser.test.ts b/extension/test/unit/parser.test.ts index 6aba70a6..760542c6 100644 --- a/extension/test/unit/parser.test.ts +++ b/extension/test/unit/parser.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert' -import { parseFlowCliVersion } from '../../src/flow-cli/cli-provider' +import { parseFlowCliVersion } from '../../src/flow-cli/binary-versions-provider' import { execDefault } from '../../src/utils/shell/exec' import { ASSERT_EQUAL } from '../globals' import * as semver from 'semver' From 35d50026401fba82f6f9d8983bdebd6cdaf66387 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 15:26:25 -0700 Subject: [PATCH 05/16] Naming fixup --- .../installers/flow-cli-installer.ts | 8 +++++--- .../src/flow-cli/binary-versions-provider.ts | 15 +++++++++------ extension/src/flow-cli/cli-provider.ts | 4 ++-- extension/src/flow-cli/cli-selection-provider.ts | 10 +++++----- extension/src/server/language-server.ts | 5 +++-- .../test/integration/1 - language-server.test.ts | 4 ++-- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/extension/src/dependency-installer/installers/flow-cli-installer.ts b/extension/src/dependency-installer/installers/flow-cli-installer.ts index e4a56dde..bededc68 100644 --- a/extension/src/dependency-installer/installers/flow-cli-installer.ts +++ b/extension/src/dependency-installer/installers/flow-cli-installer.ts @@ -6,6 +6,7 @@ import { Installer, InstallerConstructor, InstallerContext } from '../installer' import * as semver from 'semver' import fetch from 'node-fetch' import { HomebrewInstaller } from './homebrew-installer' +import { KNOWN_FLOW_COMMANDS } from '../../flow-cli/binary-versions-provider' // Command to check flow-cli const COMPATIBLE_FLOW_CLI_VERSIONS = '>=1.6.0' @@ -97,10 +98,10 @@ export class InstallFlowCLI extends Installer { } } - async checkVersion (vsn?: semver.SemVer): Promise { + async checkVersion (vsn: semver.SemVer): Promise { // Get user's version informaton this.#context.cliProvider.refresh() - const version = vsn ?? await this.#context.cliProvider.getBinaryVersions().then(x => x.find(y => y.path === 'flow')?.version) + const version = vsn if (version == null) return false if (!semver.satisfies(version, COMPATIBLE_FLOW_CLI_VERSIONS, { @@ -128,7 +129,8 @@ export class InstallFlowCLI extends Installer { async verifyInstall (): Promise { // Check if flow version is valid to verify install this.#context.cliProvider.refresh() - const version = await this.#context.cliProvider.getBinaryVersions().then(x => x.find(y => y.path === 'flow')?.version) + const installedVersions = await this.#context.cliProvider.getBinaryVersions() + const version = installedVersions.find(y => y.command === KNOWN_FLOW_COMMANDS.DEFAULT)?.version if (version == null) return false // Check flow-cli version number diff --git a/extension/src/flow-cli/binary-versions-provider.ts b/extension/src/flow-cli/binary-versions-provider.ts index 9ca6df8f..8e512f52 100644 --- a/extension/src/flow-cli/binary-versions-provider.ts +++ b/extension/src/flow-cli/binary-versions-provider.ts @@ -7,12 +7,15 @@ import { isEqual } from 'lodash' const CHECK_FLOW_CLI_CMD = (flowCommand: string): string => `${flowCommand} version --output=json` const CHECK_FLOW_CLI_CMD_NO_JSON = (flowCommand: string): string => `${flowCommand} version` -const KNOWN_BINS = ['flow', 'flow-c1'] +export enum KNOWN_FLOW_COMMANDS { + DEFAULT = 'flow', + CADENCE_V1 = 'flow-c1', +} const LEGACY_VERSION_REGEXP = /Version:\s*(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(\s|$)/m export interface CliBinary { - path: string + command: string version: semver.SemVer } @@ -26,7 +29,7 @@ export class BinaryVersionsProvider { constructor (seedBinaries: string[] = []) { // Seed the caches with the known binaries - KNOWN_BINS.forEach((bin) => { + Object.values(KNOWN_FLOW_COMMANDS).forEach((bin) => { this.add(bin) }) @@ -60,7 +63,7 @@ export class BinaryVersionsProvider { remove (path: string): void { // Known binaries cannot be removed - if (this.#caches[path] == null || KNOWN_BINS.includes(path)) return + if (this.#caches[path] == null || (Object.values(KNOWN_FLOW_COMMANDS) as string[]).includes(path)) return // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete this.#caches[path] this.#rootCache?.invalidate() @@ -85,7 +88,7 @@ export class BinaryVersionsProvider { const version: semver.SemVer | null = semver.parse(versionInfo.version) if (version === null) return null - return { path: bin, version } + return { command: bin, version } } catch { // Fallback to old method if JSON is not supported/fails return await this.#fetchBinaryInformationOld(bin) @@ -114,7 +117,7 @@ export class BinaryVersionsProvider { const version: semver.SemVer | null = semver.parse(versionStr) if (version === null) return null - return { path: bin, version } + return { command: bin, version } } catch { return null } diff --git a/extension/src/flow-cli/cli-provider.ts b/extension/src/flow-cli/cli-provider.ts index 1a4ce76f..3926e5a5 100644 --- a/extension/src/flow-cli/cli-provider.ts +++ b/extension/src/flow-cli/cli-provider.ts @@ -3,7 +3,7 @@ import { StateCache } from '../utils/state-cache' import * as vscode from 'vscode' import { Settings } from '../settings/settings' import { isEqual } from 'lodash' -import { CliBinary, BinaryVersionsProvider } from './binary-versions-provider' +import { CliBinary, BinaryVersionsProvider, KNOWN_FLOW_COMMANDS } from './binary-versions-provider' export class CliProvider { #selectedBinaryName: BehaviorSubject @@ -31,7 +31,7 @@ export class CliProvider { // Display warning to user if binary doesn't exist (only if not using the default binary) this.currentBinary$.subscribe((binary) => { - if (binary === null && this.#selectedBinaryName.getValue() !== 'flow') { + if (binary === null && this.#selectedBinaryName.getValue() !== KNOWN_FLOW_COMMANDS.DEFAULT) { void vscode.window.showErrorMessage(`The configured Flow CLI binary "${this.#selectedBinaryName.getValue()}" does not exist. Please check your settings.`) } }) diff --git a/extension/src/flow-cli/cli-selection-provider.ts b/extension/src/flow-cli/cli-selection-provider.ts index eb2364ee..ffcb12a0 100644 --- a/extension/src/flow-cli/cli-selection-provider.ts +++ b/extension/src/flow-cli/cli-selection-provider.ts @@ -75,7 +75,7 @@ export class CliSelectionProvider { } }) } else if (selected instanceof AvailableBinaryItem) { - void this.#cliProvider.setCurrentBinary(selected.path) + void this.#cliProvider.setCurrentBinary(selected.command) } })) @@ -84,7 +84,7 @@ export class CliSelectionProvider { items.push(new CustomBinaryItem()) // Hoist the current binary to the top of the list - const currentBinaryIndex = items.findIndex(item => item instanceof AvailableBinaryItem && item.path === currentBinary?.path) + const currentBinaryIndex = items.findIndex(item => item instanceof AvailableBinaryItem && item.command === currentBinary?.command) if (currentBinaryIndex != null) { const currentBinaryItem = items[currentBinaryIndex] items.splice(currentBinaryIndex, 1) @@ -135,11 +135,11 @@ class AvailableBinaryItem implements vscode.QuickPickItem { } get description (): string { - return `(${this.#binary.path})` + return `(${this.#binary.command})` } - get path (): string { - return this.#binary.path + get command (): string { + return this.#binary.command } } diff --git a/extension/src/server/language-server.ts b/extension/src/server/language-server.ts index 5ec3b245..30f3afaa 100644 --- a/extension/src/server/language-server.ts +++ b/extension/src/server/language-server.ts @@ -7,6 +7,7 @@ import { BehaviorSubject, Subscription, filter, firstValueFrom, skip } from 'rxj import { envVars } from '../utils/shell/env-vars' import { FlowConfig } from './flow-config' import { CliProvider } from '../flow-cli/cli-provider' +import { KNOWN_FLOW_COMMANDS } from '../flow-cli/binary-versions-provider' // Identities for commands handled by the Language server const RELOAD_CONFIGURATION = 'cadence.server.flow.reloadConfiguration' @@ -75,12 +76,12 @@ export class LanguageServerAPI { const accessCheckMode: string = this.#settings.getSettings().accessCheckMode const configPath: string | null = this.#config.configPath - const binaryPath = (await this.#cliProvider.getCurrentBinary())?.path + const binaryPath = (await this.#cliProvider.getCurrentBinary())?.command if (binaryPath == null) { throw new Error('No flow binary found') } - if (binaryPath !== 'flow') { + if (binaryPath !== KNOWN_FLOW_COMMANDS.DEFAULT) { try { exec('killall dlv') // Required when running language server locally on mac } catch (err) { void err } diff --git a/extension/test/integration/1 - language-server.test.ts b/extension/test/integration/1 - language-server.test.ts index c1f51a86..befb0fd6 100644 --- a/extension/test/integration/1 - language-server.test.ts +++ b/extension/test/integration/1 - language-server.test.ts @@ -33,7 +33,7 @@ suite('Language Server & Emulator Integration', () => { // create a mock cli provider without invokign the constructor cliBinary$ = new BehaviorSubject({ - path: 'flow', + command: 'flow', version: new SemVer('1.0.0') }) const mockCliProvider = { @@ -63,7 +63,7 @@ suite('Language Server & Emulator Integration', () => { fileModified$.next() pathChanged$.next('foo') cliBinary$.next({ - path: 'flow', + command: 'flow', version: new SemVer('1.0.1') }) From 6cc92fc0c894806f0940e12943d83cd07e280461 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 15:37:04 -0700 Subject: [PATCH 06/16] fix hoisting --- extension/src/flow-cli/cli-selection-provider.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extension/src/flow-cli/cli-selection-provider.ts b/extension/src/flow-cli/cli-selection-provider.ts index ffcb12a0..4fd2df59 100644 --- a/extension/src/flow-cli/cli-selection-provider.ts +++ b/extension/src/flow-cli/cli-selection-provider.ts @@ -84,8 +84,12 @@ export class CliSelectionProvider { items.push(new CustomBinaryItem()) // Hoist the current binary to the top of the list - const currentBinaryIndex = items.findIndex(item => item instanceof AvailableBinaryItem && item.command === currentBinary?.command) - if (currentBinaryIndex != null) { + const currentBinaryIndex = items.findIndex(item => + item instanceof AvailableBinaryItem && + currentBinary != null && + item.command === currentBinary.command + ) + if (currentBinaryIndex !== -1) { const currentBinaryItem = items[currentBinaryIndex] items.splice(currentBinaryIndex, 1) items.unshift(currentBinaryItem) From 2a215568c1616f6851b2fabe041296e0203f495d Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 15:42:17 -0700 Subject: [PATCH 07/16] rename to command --- extension/src/flow-cli/binary-versions-provider.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extension/src/flow-cli/binary-versions-provider.ts b/extension/src/flow-cli/binary-versions-provider.ts index 8e512f52..fd44f0db 100644 --- a/extension/src/flow-cli/binary-versions-provider.ts +++ b/extension/src/flow-cli/binary-versions-provider.ts @@ -52,20 +52,20 @@ export class BinaryVersionsProvider { }) } - add (path: string): void { - if (this.#caches[path] != null) return - this.#caches[path] = new StateCache(async () => await this.#fetchBinaryInformation(path)) - this.#caches[path].subscribe(() => { + add (command: string): void { + if (this.#caches[command] != null) return + this.#caches[command] = new StateCache(async () => await this.#fetchBinaryInformation(command)) + this.#caches[command].subscribe(() => { this.#rootCache?.invalidate() }) this.#rootCache?.invalidate() } - remove (path: string): void { + remove (command: string): void { // Known binaries cannot be removed - if (this.#caches[path] == null || (Object.values(KNOWN_FLOW_COMMANDS) as string[]).includes(path)) return + if (this.#caches[command] == null || (Object.values(KNOWN_FLOW_COMMANDS) as string[]).includes(command)) return // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete this.#caches[path] + delete this.#caches[command] this.#rootCache?.invalidate() } From ef6097916758042c87954f2ba053d0fe3abbab9a Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 15:58:50 -0700 Subject: [PATCH 08/16] address feedback --- .../src/dependency-installer/installers/flow-cli-installer.ts | 3 +-- extension/src/flow-cli/binary-versions-provider.ts | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/extension/src/dependency-installer/installers/flow-cli-installer.ts b/extension/src/dependency-installer/installers/flow-cli-installer.ts index bededc68..50b03415 100644 --- a/extension/src/dependency-installer/installers/flow-cli-installer.ts +++ b/extension/src/dependency-installer/installers/flow-cli-installer.ts @@ -98,10 +98,9 @@ export class InstallFlowCLI extends Installer { } } - async checkVersion (vsn: semver.SemVer): Promise { + async checkVersion (version: semver.SemVer): Promise { // Get user's version informaton this.#context.cliProvider.refresh() - const version = vsn if (version == null) return false if (!semver.satisfies(version, COMPATIBLE_FLOW_CLI_VERSIONS, { diff --git a/extension/src/flow-cli/binary-versions-provider.ts b/extension/src/flow-cli/binary-versions-provider.ts index fd44f0db..ac2bb01f 100644 --- a/extension/src/flow-cli/binary-versions-provider.ts +++ b/extension/src/flow-cli/binary-versions-provider.ts @@ -12,6 +12,8 @@ export enum KNOWN_FLOW_COMMANDS { CADENCE_V1 = 'flow-c1', } +// This regex matches a string like "Version: v{SEMVER}" and extracts the version number +// It uses the official semver regex from https://semver.org/ const LEGACY_VERSION_REGEXP = /Version:\s*(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(\s|$)/m export interface CliBinary { From f2384609de2e7bcea717648a12a1c9453bdff288 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 16:05:27 -0700 Subject: [PATCH 09/16] catch error --- .../installers/flow-cli-installer.ts | 5 ++++- extension/src/flow-cli/cli-provider.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/extension/src/dependency-installer/installers/flow-cli-installer.ts b/extension/src/dependency-installer/installers/flow-cli-installer.ts index 50b03415..17375089 100644 --- a/extension/src/dependency-installer/installers/flow-cli-installer.ts +++ b/extension/src/dependency-installer/installers/flow-cli-installer.ts @@ -128,7 +128,10 @@ export class InstallFlowCLI extends Installer { async verifyInstall (): Promise { // Check if flow version is valid to verify install this.#context.cliProvider.refresh() - const installedVersions = await this.#context.cliProvider.getBinaryVersions() + const installedVersions = await this.#context.cliProvider.getBinaryVersions().catch((e) => { + window.showErrorMessage('Failed to check CLI version: ' + e.message) + return [] + }) const version = installedVersions.find(y => y.command === KNOWN_FLOW_COMMANDS.DEFAULT)?.version if (version == null) return false diff --git a/extension/src/flow-cli/cli-provider.ts b/extension/src/flow-cli/cli-provider.ts index 3926e5a5..963c456b 100644 --- a/extension/src/flow-cli/cli-provider.ts +++ b/extension/src/flow-cli/cli-provider.ts @@ -8,18 +8,18 @@ import { CliBinary, BinaryVersionsProvider, KNOWN_FLOW_COMMANDS } from './binary export class CliProvider { #selectedBinaryName: BehaviorSubject #currentBinary$: StateCache - #binaryVersions: BinaryVersionsProvider + #binaryVersionsProvider: BinaryVersionsProvider #settings: Settings constructor (settings: Settings) { const initialBinaryPath = settings.getSettings().flowCommand this.#settings = settings - this.#binaryVersions = new BinaryVersionsProvider([initialBinaryPath]) + this.#binaryVersionsProvider = new BinaryVersionsProvider([initialBinaryPath]) this.#selectedBinaryName = new BehaviorSubject(initialBinaryPath) this.#currentBinary$ = new StateCache(async () => { const name: string = this.#selectedBinaryName.getValue() - const versionCache = this.#binaryVersions.get(name) + const versionCache = this.#binaryVersionsProvider.get(name) if (versionCache == null) return null return await versionCache.getValue() }) @@ -43,10 +43,10 @@ export class CliProvider { // Subscribe to changes in the selected binary to update the caches this.#selectedBinaryName.pipe(distinctUntilChanged(), startWith(null), pairwise()).subscribe(([prev, curr]) => { // Remove the previous binary from the cache - if (prev != null) this.#binaryVersions.remove(prev) + if (prev != null) this.#binaryVersionsProvider.remove(prev) // Add the current binary to the cache - if (curr != null) this.#binaryVersions.add(curr) + if (curr != null) this.#binaryVersionsProvider.add(curr) // Invalidate the current binary cache this.#currentBinary$.invalidate() @@ -66,15 +66,15 @@ export class CliProvider { } async getBinaryVersions (): Promise { - return await this.#binaryVersions.getVersions() + return await this.#binaryVersionsProvider.getVersions() } get binaryVersions$ (): Observable { - return this.#binaryVersions.versions$.pipe(distinctUntilChanged(isEqual)) + return this.#binaryVersionsProvider.versions$.pipe(distinctUntilChanged(isEqual)) } // Refresh all cached binary versions refresh (): void { - this.#binaryVersions.refresh() + this.#binaryVersionsProvider.refresh() } } From 59e57a6bcd1762102c4c84d4e75935aec1a90ac0 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 15 Apr 2024 16:37:06 -0700 Subject: [PATCH 10/16] format --- .../src/dependency-installer/installers/flow-cli-installer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/dependency-installer/installers/flow-cli-installer.ts b/extension/src/dependency-installer/installers/flow-cli-installer.ts index 17375089..81dee013 100644 --- a/extension/src/dependency-installer/installers/flow-cli-installer.ts +++ b/extension/src/dependency-installer/installers/flow-cli-installer.ts @@ -129,7 +129,7 @@ export class InstallFlowCLI extends Installer { // Check if flow version is valid to verify install this.#context.cliProvider.refresh() const installedVersions = await this.#context.cliProvider.getBinaryVersions().catch((e) => { - window.showErrorMessage('Failed to check CLI version: ' + e.message) + void window.showErrorMessage(`Failed to check CLI version: ${String(e.message)}`) return [] }) const version = installedVersions.find(y => y.command === KNOWN_FLOW_COMMANDS.DEFAULT)?.version From 68bf6b97c561ed7251ea61e4e73790c39fd28d84 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 16 Apr 2024 07:59:55 -0700 Subject: [PATCH 11/16] rename & fix global configuration --- .../installers/flow-cli-installer.ts | 2 +- extension/src/flow-cli/cli-provider.ts | 24 +++++++++++-------- .../src/flow-cli/cli-selection-provider.ts | 2 +- ...s-provider.ts => cli-versions-provider.ts} | 2 +- extension/src/server/language-server.ts | 2 +- .../integration/1 - language-server.test.ts | 2 +- extension/test/unit/parser.test.ts | 2 +- 7 files changed, 20 insertions(+), 16 deletions(-) rename extension/src/flow-cli/{binary-versions-provider.ts => cli-versions-provider.ts} (99%) diff --git a/extension/src/dependency-installer/installers/flow-cli-installer.ts b/extension/src/dependency-installer/installers/flow-cli-installer.ts index 81dee013..fa8f971b 100644 --- a/extension/src/dependency-installer/installers/flow-cli-installer.ts +++ b/extension/src/dependency-installer/installers/flow-cli-installer.ts @@ -6,7 +6,7 @@ import { Installer, InstallerConstructor, InstallerContext } from '../installer' import * as semver from 'semver' import fetch from 'node-fetch' import { HomebrewInstaller } from './homebrew-installer' -import { KNOWN_FLOW_COMMANDS } from '../../flow-cli/binary-versions-provider' +import { KNOWN_FLOW_COMMANDS } from '../../flow-cli/cli-versions-provider' // Command to check flow-cli const COMPATIBLE_FLOW_CLI_VERSIONS = '>=1.6.0' diff --git a/extension/src/flow-cli/cli-provider.ts b/extension/src/flow-cli/cli-provider.ts index 963c456b..dd9bdb39 100644 --- a/extension/src/flow-cli/cli-provider.ts +++ b/extension/src/flow-cli/cli-provider.ts @@ -3,23 +3,23 @@ import { StateCache } from '../utils/state-cache' import * as vscode from 'vscode' import { Settings } from '../settings/settings' import { isEqual } from 'lodash' -import { CliBinary, BinaryVersionsProvider, KNOWN_FLOW_COMMANDS } from './binary-versions-provider' +import { CliBinary, CliVersionsProvider, KNOWN_FLOW_COMMANDS } from './cli-versions-provider' export class CliProvider { #selectedBinaryName: BehaviorSubject #currentBinary$: StateCache - #binaryVersionsProvider: BinaryVersionsProvider + #cliVersionsProvider: CliVersionsProvider #settings: Settings constructor (settings: Settings) { const initialBinaryPath = settings.getSettings().flowCommand this.#settings = settings - this.#binaryVersionsProvider = new BinaryVersionsProvider([initialBinaryPath]) + this.#cliVersionsProvider = new CliVersionsProvider([initialBinaryPath]) this.#selectedBinaryName = new BehaviorSubject(initialBinaryPath) this.#currentBinary$ = new StateCache(async () => { const name: string = this.#selectedBinaryName.getValue() - const versionCache = this.#binaryVersionsProvider.get(name) + const versionCache = this.#cliVersionsProvider.get(name) if (versionCache == null) return null return await versionCache.getValue() }) @@ -43,10 +43,10 @@ export class CliProvider { // Subscribe to changes in the selected binary to update the caches this.#selectedBinaryName.pipe(distinctUntilChanged(), startWith(null), pairwise()).subscribe(([prev, curr]) => { // Remove the previous binary from the cache - if (prev != null) this.#binaryVersionsProvider.remove(prev) + if (prev != null) this.#cliVersionsProvider.remove(prev) // Add the current binary to the cache - if (curr != null) this.#binaryVersionsProvider.add(curr) + if (curr != null) this.#cliVersionsProvider.add(curr) // Invalidate the current binary cache this.#currentBinary$.invalidate() @@ -58,7 +58,11 @@ export class CliProvider { } async setCurrentBinary (name: string): Promise { - await this.#settings.updateSettings({ flowCommand: name }) + if (vscode.workspace.workspaceFolders == null) { + await this.#settings.updateSettings({ flowCommand: name }, vscode.ConfigurationTarget.Global) + } else { + await this.#settings.updateSettings({ flowCommand: name }) + } } get currentBinary$ (): Observable { @@ -66,15 +70,15 @@ export class CliProvider { } async getBinaryVersions (): Promise { - return await this.#binaryVersionsProvider.getVersions() + return await this.#cliVersionsProvider.getVersions() } get binaryVersions$ (): Observable { - return this.#binaryVersionsProvider.versions$.pipe(distinctUntilChanged(isEqual)) + return this.#cliVersionsProvider.versions$.pipe(distinctUntilChanged(isEqual)) } // Refresh all cached binary versions refresh (): void { - this.#binaryVersionsProvider.refresh() + this.#cliVersionsProvider.refresh() } } diff --git a/extension/src/flow-cli/cli-selection-provider.ts b/extension/src/flow-cli/cli-selection-provider.ts index 4fd2df59..999f6f46 100644 --- a/extension/src/flow-cli/cli-selection-provider.ts +++ b/extension/src/flow-cli/cli-selection-provider.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode' import { zip } from 'rxjs' import { CliProvider } from './cli-provider' import { SemVer } from 'semver' -import { CliBinary } from './binary-versions-provider' +import { CliBinary } from './cli-versions-provider' const CHANGE_CLI_BINARY = 'cadence.changeFlowCliBinary' const CADENCE_V1_CLI_REGEX = /-cadence-v1.0.0/g diff --git a/extension/src/flow-cli/binary-versions-provider.ts b/extension/src/flow-cli/cli-versions-provider.ts similarity index 99% rename from extension/src/flow-cli/binary-versions-provider.ts rename to extension/src/flow-cli/cli-versions-provider.ts index ac2bb01f..c10d97c0 100644 --- a/extension/src/flow-cli/binary-versions-provider.ts +++ b/extension/src/flow-cli/cli-versions-provider.ts @@ -25,7 +25,7 @@ interface FlowVersionOutput { version: string } -export class BinaryVersionsProvider { +export class CliVersionsProvider { #rootCache: StateCache #caches: { [key: string]: StateCache } = {} diff --git a/extension/src/server/language-server.ts b/extension/src/server/language-server.ts index 30f3afaa..ad74ca80 100644 --- a/extension/src/server/language-server.ts +++ b/extension/src/server/language-server.ts @@ -7,7 +7,7 @@ import { BehaviorSubject, Subscription, filter, firstValueFrom, skip } from 'rxj import { envVars } from '../utils/shell/env-vars' import { FlowConfig } from './flow-config' import { CliProvider } from '../flow-cli/cli-provider' -import { KNOWN_FLOW_COMMANDS } from '../flow-cli/binary-versions-provider' +import { KNOWN_FLOW_COMMANDS } from '../flow-cli/cli-versions-provider' // Identities for commands handled by the Language server const RELOAD_CONFIGURATION = 'cadence.server.flow.reloadConfiguration' diff --git a/extension/test/integration/1 - language-server.test.ts b/extension/test/integration/1 - language-server.test.ts index befb0fd6..47c9ab36 100644 --- a/extension/test/integration/1 - language-server.test.ts +++ b/extension/test/integration/1 - language-server.test.ts @@ -9,7 +9,7 @@ import { BehaviorSubject, Subject } from 'rxjs' import { State } from 'vscode-languageclient' import * as sinon from 'sinon' import { SemVer } from 'semver' -import { CliBinary } from '../../src/flow-cli/binary-versions-provider' +import { CliBinary } from '../../src/flow-cli/cli-versions-provider' suite('Language Server & Emulator Integration', () => { let LS: LanguageServerAPI diff --git a/extension/test/unit/parser.test.ts b/extension/test/unit/parser.test.ts index 760542c6..5f5e9ec8 100644 --- a/extension/test/unit/parser.test.ts +++ b/extension/test/unit/parser.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert' -import { parseFlowCliVersion } from '../../src/flow-cli/binary-versions-provider' +import { parseFlowCliVersion } from '../../src/flow-cli/cli-versions-provider' import { execDefault } from '../../src/utils/shell/exec' import { ASSERT_EQUAL } from '../globals' import * as semver from 'semver' From 9836e10dcda4859ca62ce0f43ce880f8cec57b1c Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 16 Apr 2024 11:25:06 -0700 Subject: [PATCH 12/16] simplify regex --- extension/src/flow-cli/cli-versions-provider.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/extension/src/flow-cli/cli-versions-provider.ts b/extension/src/flow-cli/cli-versions-provider.ts index c10d97c0..6d0231c7 100644 --- a/extension/src/flow-cli/cli-versions-provider.ts +++ b/extension/src/flow-cli/cli-versions-provider.ts @@ -12,9 +12,8 @@ export enum KNOWN_FLOW_COMMANDS { CADENCE_V1 = 'flow-c1', } -// This regex matches a string like "Version: v{SEMVER}" and extracts the version number -// It uses the official semver regex from https://semver.org/ -const LEGACY_VERSION_REGEXP = /Version:\s*(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(\s|$)/m +// Matches the version number from the output of the Flow CLI +const LEGACY_VERSION_REGEXP = /Version:\s*v(.*)(?\s|$)/m export interface CliBinary { command: string @@ -112,9 +111,6 @@ export class CliVersionsProvider { versionStr = parseFlowCliVersion(output.stderr) } - versionStr = versionStr != null ? semver.clean(versionStr) : null - if (versionStr === null) return null - // Ensure user has a compatible version number installed const version: semver.SemVer | null = semver.parse(versionStr) if (version === null) return null @@ -142,5 +138,7 @@ export class CliVersionsProvider { } export function parseFlowCliVersion (buffer: Buffer | string): string | null { - return buffer.toString().match(LEGACY_VERSION_REGEXP)?.[1] ?? null + const rawMatch = buffer.toString().match(LEGACY_VERSION_REGEXP)?.[1] ?? null + if (rawMatch == null) return null + return semver.clean(rawMatch) } From 72b4f2de6391c2d2e280e3731218f20788745f6b Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 16 Apr 2024 13:55:49 -0700 Subject: [PATCH 13/16] fix non capture group --- extension/src/flow-cli/cli-versions-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/flow-cli/cli-versions-provider.ts b/extension/src/flow-cli/cli-versions-provider.ts index 6d0231c7..8ac4ae55 100644 --- a/extension/src/flow-cli/cli-versions-provider.ts +++ b/extension/src/flow-cli/cli-versions-provider.ts @@ -13,7 +13,7 @@ export enum KNOWN_FLOW_COMMANDS { } // Matches the version number from the output of the Flow CLI -const LEGACY_VERSION_REGEXP = /Version:\s*v(.*)(?\s|$)/m +const LEGACY_VERSION_REGEXP = /Version:\s*v(.*)(?:\s|$)/m export interface CliBinary { command: string From 1595bce11299d1f4ce9c6248f7f9ad41db03d0a2 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 16 Apr 2024 14:05:43 -0700 Subject: [PATCH 14/16] fix test --- extension/test/unit/parser.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/test/unit/parser.test.ts b/extension/test/unit/parser.test.ts index 5f5e9ec8..41f48829 100644 --- a/extension/test/unit/parser.test.ts +++ b/extension/test/unit/parser.test.ts @@ -8,17 +8,17 @@ suite('Parsing Unit Tests', () => { test('Flow CLI Version Parsing (buffer input)', async () => { let versionTest: Buffer = Buffer.from('Version: v0.1.0\nCommit: 0a1b2c3d') let formatted = parseFlowCliVersion(versionTest) - ASSERT_EQUAL(formatted, 'v0.1.0') + ASSERT_EQUAL(formatted, '0.1.0') versionTest = Buffer.from('Version: v0.1.0') formatted = parseFlowCliVersion(versionTest) - ASSERT_EQUAL(formatted, 'v0.1.0') + ASSERT_EQUAL(formatted, '0.1.0') }) test('Flow CLI Version Parsing (string input)', async () => { const versionTest: string = 'Version: v0.1.0\nCommit: 0a1b2c3d' const formatted = parseFlowCliVersion(versionTest) - ASSERT_EQUAL(formatted, 'v0.1.0') + ASSERT_EQUAL(formatted, '0.1.0') }) test('Flow CLI Version Parsing produces valid semver from Flow CLI output', async () => { From 5a71f4e10d6cc2eac8e2e4cb2e41a2bddbbe7714 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 16 Apr 2024 16:05:58 -0700 Subject: [PATCH 15/16] Fix version selector re-appearing --- extension/src/flow-cli/cli-selection-provider.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extension/src/flow-cli/cli-selection-provider.ts b/extension/src/flow-cli/cli-selection-provider.ts index 999f6f46..29034bf0 100644 --- a/extension/src/flow-cli/cli-selection-provider.ts +++ b/extension/src/flow-cli/cli-selection-provider.ts @@ -79,6 +79,10 @@ export class CliSelectionProvider { } })) + this.#disposables.push(versionSelector.onDidHide(() => { + void this.#toggleSelector(false) + })) + // Update available versions const items: Array = availableBinaries.map(binary => new AvailableBinaryItem(binary)) items.push(new CustomBinaryItem()) From 7e4282299725c1830def59a0614f788130412cfb Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Tue, 16 Apr 2024 17:00:00 -0700 Subject: [PATCH 16/16] combine overlapping logic --- .../src/flow-cli/cli-versions-provider.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/extension/src/flow-cli/cli-versions-provider.ts b/extension/src/flow-cli/cli-versions-provider.ts index 8ac4ae55..035677ac 100644 --- a/extension/src/flow-cli/cli-versions-provider.ts +++ b/extension/src/flow-cli/cli-versions-provider.ts @@ -85,11 +85,7 @@ export class CliVersionsProvider { // Format version string from output const versionInfo: FlowVersionOutput = JSON.parse(buffer) - // Ensure user has a compatible version number installed - const version: semver.SemVer | null = semver.parse(versionInfo.version) - if (version === null) return null - - return { command: bin, version } + return cliBinaryFromVersion(bin, versionInfo.version) } catch { // Fallback to old method if JSON is not supported/fails return await this.#fetchBinaryInformationOld(bin) @@ -111,11 +107,9 @@ export class CliVersionsProvider { versionStr = parseFlowCliVersion(output.stderr) } - // Ensure user has a compatible version number installed - const version: semver.SemVer | null = semver.parse(versionStr) - if (version === null) return null + if (versionStr == null) return null - return { command: bin, version } + return cliBinaryFromVersion(bin, versionStr) } catch { return null } @@ -142,3 +136,11 @@ export function parseFlowCliVersion (buffer: Buffer | string): string | null { if (rawMatch == null) return null return semver.clean(rawMatch) } + +function cliBinaryFromVersion (bin: string, versionStr: string): CliBinary | null { + // Ensure user has a compatible version number installed + const version: semver.SemVer | null = semver.parse(versionStr) + if (version === null) return null + + return { command: bin, version } +}