Skip to content

Commit

Permalink
Refactor FlowVersionProvider & use cadence.flowCommand from Settings (
Browse files Browse the repository at this point in the history
  • Loading branch information
jribbink authored Jan 31, 2024
1 parent c6b75e6 commit 003e335
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 61 deletions.
6 changes: 4 additions & 2 deletions extension/src/dependency-installer/dependency-installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Installer, InstallerConstructor, InstallerContext, InstallError } from
import { promptUserErrorMessage } from '../ui/prompts'
import { StateCache } from '../utils/state-cache'
import { LanguageServerAPI } from '../server/language-server'
import { FlowVersionProvider } from '../flow-cli/flow-version-provider'

const INSTALLERS: InstallerConstructor[] = [
InstallFlowCLI
Expand All @@ -14,10 +15,11 @@ export class DependencyInstaller {
missingDependencies: StateCache<Installer[]>
#installerContext: InstallerContext

constructor (languageServer: LanguageServerAPI) {
constructor (languageServerApi: LanguageServerAPI, flowVersionProvider: FlowVersionProvider) {
this.#installerContext = {
refreshDependencies: this.checkDependencies.bind(this),
langaugeServerApi: languageServer
languageServerApi,
flowVersionProvider
}
this.#registerInstallers()

Expand Down
4 changes: 3 additions & 1 deletion extension/src/dependency-installer/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import { window } from 'vscode'
import { envVars } from '../utils/shell/env-vars'
import { LanguageServerAPI } from '../server/language-server'
import { FlowVersionProvider } from '../flow-cli/flow-version-provider'

// InstallError is thrown if install fails
export class InstallError extends Error {}

export interface InstallerContext {
refreshDependencies: () => Promise<void>
langaugeServerApi: LanguageServerAPI
languageServerApi: LanguageServerAPI
flowVersionProvider: FlowVersionProvider
}

export type InstallerConstructor = new (context: InstallerContext) => Installer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Installer, InstallerConstructor, InstallerContext } from '../installer'
import * as semver from 'semver'
import fetch from 'node-fetch'
import { HomebrewInstaller } from './homebrew-installer'
import { flowVersion } from '../../utils/flow-version'

// Command to check flow-cli
const COMPATIBLE_FLOW_CLI_VERSIONS = '>=1.6.0'
Expand Down Expand Up @@ -39,8 +38,8 @@ export class InstallFlowCLI extends Installer {
}

async install (): Promise<void> {
const isActive = this.#context.langaugeServerApi.isActive ?? false
if (isActive) await this.#context.langaugeServerApi.deactivate()
const isActive = this.#context.languageServerApi.isActive ?? false
if (isActive) await this.#context.languageServerApi.deactivate()
const OS_TYPE = process.platform

try {
Expand All @@ -58,7 +57,7 @@ export class InstallFlowCLI extends Installer {
} catch {
void window.showErrorMessage('Failed to install Flow CLI')
}
if (isActive) await this.#context.langaugeServerApi.activate()
if (isActive) await this.#context.languageServerApi.activate()
}

async #install_macos (): Promise<void> {
Expand Down Expand Up @@ -98,7 +97,8 @@ export class InstallFlowCLI extends Installer {

async checkVersion (vsn?: semver.SemVer): Promise<boolean> {
// Get user's version informaton
const version = vsn ?? await flowVersion.getValue(true)
this.#context.flowVersionProvider.refresh()
const version = vsn ?? await this.#context.flowVersionProvider.getVersion()
if (version === null) return false

if (!semver.satisfies(version, COMPATIBLE_FLOW_CLI_VERSIONS, {
Expand All @@ -123,7 +123,8 @@ export class InstallFlowCLI extends Installer {

async verifyInstall (): Promise<boolean> {
// Check if flow version is valid to verify install
const version = await flowVersion.getValue(true)
this.#context.flowVersionProvider.refresh()
const version = await this.#context.flowVersionProvider.getVersion()
if (version == null) return false

// Check flow-cli version number
Expand Down
9 changes: 6 additions & 3 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { CommandController } from './commands/command-controller'
import { ExtensionContext } from 'vscode'
import { DependencyInstaller } from './dependency-installer/dependency-installer'
import { Settings } from './settings/settings'
import { FlowVersionProvider } from './flow-cli/flow-version-provider'
import { JSONSchemaProvider } from './json-schema-provider'
import { flowVersion } from './utils/flow-version'
import { LanguageServerAPI } from './server/language-server'
import { FlowConfig } from './server/flow-config'
import { TestProvider } from './test-provider/test-provider'
Expand Down Expand Up @@ -33,8 +33,11 @@ export class Extension {
private constructor (settings: Settings, ctx: ExtensionContext | undefined) {
this.ctx = ctx

// Register Flow version provider
const flowVersionProvider = new FlowVersionProvider(settings)

// Register JSON schema provider
if (ctx != null) JSONSchemaProvider.register(ctx, flowVersion)
if (ctx != null) JSONSchemaProvider.register(ctx, flowVersionProvider.state$)

// Initialize Flow Config
const flowConfig = new FlowConfig(settings)
Expand All @@ -47,7 +50,7 @@ export class Extension {
// The language server will start if all dependencies are installed
// Otherwise, the language server will not start and will start after
// the user installs the missing dependencies
this.#dependencyInstaller = new DependencyInstaller(this.languageServer)
this.#dependencyInstaller = new DependencyInstaller(this.languageServer, flowVersionProvider)
this.#dependencyInstaller.missingDependencies.subscribe((missing) => {
if (missing.length === 0) {
void this.languageServer.activate()
Expand Down
57 changes: 57 additions & 0 deletions extension/src/flow-cli/flow-version-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Settings } from '../settings/settings'
import { execDefault } from '../utils/shell/exec'
import { StateCache } from '../utils/state-cache'
import * as semver from 'semver'

const CHECK_FLOW_CLI_CMD = (flowCommand: string): string => `${flowCommand} version`

export class FlowVersionProvider {
#settings: Settings
#stateCache: StateCache<semver.SemVer | null>
#parseCliVersion: (buffer: Buffer | string) => string

constructor (settings: Settings, parseCliVersion: (buffer: Buffer | string) => string = parseFlowCliVersion) {
this.#stateCache = new StateCache<semver.SemVer | null>(async () => await this.#fetchFlowVersion())
this.#settings = settings
this.#parseCliVersion = parseCliVersion
}

async #fetchFlowVersion (): Promise<semver.SemVer | null> {
try {
// Get user's version informaton
const buffer: string = (await execDefault(CHECK_FLOW_CLI_CMD(
this.#settings.getSettings().flowCommand
))).stdout

// Format version string from output
let versionStr: string | null = this.#parseCliVersion(buffer)

versionStr = semver.clean(versionStr)
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 version
} catch {
return null
}
}

refresh (): void {
this.#stateCache.invalidate()
}

async getVersion (): Promise<semver.SemVer | null> {
return await this.#stateCache.getValue()
}

get state$ (): StateCache<semver.SemVer | null> {
return this.#stateCache
}
}

export function parseFlowCliVersion (buffer: Buffer | string): string {
return (buffer.toString().split('\n')[0]).split(' ')[1]
}
32 changes: 0 additions & 32 deletions extension/src/utils/flow-version.ts

This file was deleted.

41 changes: 26 additions & 15 deletions extension/test/integration/0 - dependencies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@ import { DependencyInstaller } from '../../src/dependency-installer/dependency-i
import { MaxTimeout } from '../globals'
import { InstallFlowCLI } from '../../src/dependency-installer/installers/flow-cli-installer'
import { stub } from 'sinon'
import { before } from 'mocha'
import { FlowVersionProvider } from '../../src/flow-cli/flow-version-provider'
import { getMockSettings } from '../mock/mockSettings'

// Note: Dependency installation must run before other integration tests
suite('Dependency Installer', () => {
let flowVersionProvider: any

before(async function () {
flowVersionProvider = new FlowVersionProvider(getMockSettings())
})

test('Install Missing Dependencies', async () => {
const mockLangaugeServerApi = {
const mockLanguageServerApi = {
activate: stub(),
deactivate: stub(),
isActive: true
}
const dependencyManager = new DependencyInstaller(mockLangaugeServerApi as any)
const dependencyManager = new DependencyInstaller(mockLanguageServerApi as any, flowVersionProvider)
await assert.doesNotReject(async () => { await dependencyManager.installMissing() })

// Check that all dependencies are installed
Expand All @@ -21,45 +30,47 @@ suite('Dependency Installer', () => {
}).timeout(MaxTimeout)

test('Flow CLI installer restarts langauge server if active', async () => {
const mockLangaugeServerApi = {
const mockLanguageServerApi = {
activate: stub().callsFake(async () => {
mockLangaugeServerApi.isActive = true
mockLanguageServerApi.isActive = true
}),
deactivate: stub().callsFake(async () => {
mockLangaugeServerApi.isActive = false
mockLanguageServerApi.isActive = false
}),
isActive: true
}
const mockInstallerContext = {
refreshDependencies: async () => {},
langaugeServerApi: mockLangaugeServerApi as any
languageServerApi: mockLanguageServerApi as any,
flowVersionProvider
}
const flowCliInstaller = new InstallFlowCLI(mockInstallerContext)

await assert.doesNotReject(async () => { await flowCliInstaller.install() })
assert(mockLangaugeServerApi.deactivate.calledOnce)
assert(mockLangaugeServerApi.activate.calledOnce)
assert(mockLangaugeServerApi.deactivate.calledBefore(mockLangaugeServerApi.activate))
assert(mockLanguageServerApi.deactivate.calledOnce)
assert(mockLanguageServerApi.activate.calledOnce)
assert(mockLanguageServerApi.deactivate.calledBefore(mockLanguageServerApi.activate))
}).timeout(MaxTimeout)

test('Flow CLI installer does not restart langauge server if inactive', async () => {
const mockLangaugeServerApi = {
const mockLanguageServerApi = {
activate: stub().callsFake(async () => {
mockLangaugeServerApi.isActive = true
mockLanguageServerApi.isActive = true
}),
deactivate: stub().callsFake(async () => {
mockLangaugeServerApi.isActive = false
mockLanguageServerApi.isActive = false
}),
isActive: false
}
const mockInstallerContext = {
refreshDependencies: async () => {},
langaugeServerApi: mockLangaugeServerApi as any
languageServerApi: mockLanguageServerApi as any,
flowVersionProvider
}
const flowCliInstaller = new InstallFlowCLI(mockInstallerContext)

await assert.doesNotReject(async () => { await flowCliInstaller.install() })
assert(mockLangaugeServerApi.activate.notCalled)
assert(mockLangaugeServerApi.deactivate.notCalled)
assert(mockLanguageServerApi.activate.notCalled)
assert(mockLanguageServerApi.deactivate.notCalled)
}).timeout(MaxTimeout)
})
20 changes: 18 additions & 2 deletions extension/test/unit/parser.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { parseFlowCliVersion } from '../../src/utils/flow-version'
import * as assert from 'assert'
import { parseFlowCliVersion } from '../../src/flow-cli/flow-version-provider'
import { execDefault } from '../../src/utils/shell/exec'
import { ASSERT_EQUAL } from '../globals'
import * as semver from 'semver'

suite('Parsing Unit Tests', () => {
test('Flow CLI Version Parsing', async () => {
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')
Expand All @@ -11,4 +14,17 @@ suite('Parsing Unit Tests', () => {
formatted = parseFlowCliVersion(versionTest)
ASSERT_EQUAL(formatted, 'v0.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')
})

test('Flow CLI Version Parsing produces valid semver from Flow CLI output', async () => {
// Check that version is parsed from currently installed flow-cli
const { stdout } = await execDefault('flow version')
const formatted = parseFlowCliVersion(stdout)
assert(semver.valid(formatted))
})
})

0 comments on commit 003e335

Please sign in to comment.