1
1
import { BehaviorSubject , Observable , distinctUntilChanged , pairwise , startWith } from 'rxjs'
2
- import { execDefault } from '../utils/shell/exec'
3
2
import { StateCache } from '../utils/state-cache'
4
- import * as semver from 'semver'
5
3
import * as vscode from 'vscode'
6
4
import { Settings } from '../settings/settings'
7
5
import { isEqual } from 'lodash'
8
-
9
- const CHECK_FLOW_CLI_CMD = ( flowCommand : string ) : string => `${ flowCommand } version --output=json`
10
- const CHECK_FLOW_CLI_CMD_NO_JSON = ( flowCommand : string ) : string => `${ flowCommand } version`
11
-
12
- const KNOWN_BINS = [ 'flow' , 'flow-c1' ]
13
-
14
- const CADENCE_V1_CLI_REGEX = / - c a d e n c e - v 1 .0 .0 / g
15
- const LEGACY_VERSION_REGEXP = / V e r s i o n : \s * ( v ? ( 0 | [ 1 - 9 ] \d * ) \. ( 0 | [ 1 - 9 ] \d * ) \. ( 0 | [ 1 - 9 ] \d * ) (?: - ( (?: 0 | [ 1 - 9 ] \d * | \d * [ a - z A - Z - ] [ 0 - 9 a - z A - Z - ] * ) (?: \. (?: 0 | [ 1 - 9 ] \d * | \d * [ a - z A - Z - ] [ 0 - 9 a - z A - Z - ] * ) ) * ) ) ? (?: \+ ( [ 0 - 9 a - z A - Z - ] + (?: \. [ 0 - 9 a - z A - Z - ] + ) * ) ) ? ) ( \s | $ ) / m
16
-
17
- export interface CliBinary {
18
- name : string
19
- version : semver . SemVer
20
- }
21
-
22
- interface FlowVersionOutput {
23
- version : string
24
- }
25
-
26
- interface AvailableBinariesCache {
27
- [ key : string ] : StateCache < CliBinary | null >
28
- }
6
+ import { CliBinary , CliVersionsProvider , KNOWN_FLOW_COMMANDS } from './cli-versions-provider'
29
7
30
8
export class CliProvider {
31
9
#selectedBinaryName: BehaviorSubject < string >
32
10
#currentBinary$: StateCache < CliBinary | null >
33
- #availableBinaries: AvailableBinariesCache = { }
34
- #availableBinaries$: StateCache < CliBinary [ ] >
11
+ #cliVersionsProvider: CliVersionsProvider
35
12
#settings: Settings
36
13
37
14
constructor ( settings : Settings ) {
15
+ const initialBinaryPath = settings . getSettings ( ) . flowCommand
16
+
38
17
this . #settings = settings
18
+ this . #cliVersionsProvider = new CliVersionsProvider ( [ initialBinaryPath ] )
19
+ this . #selectedBinaryName = new BehaviorSubject < string > ( initialBinaryPath )
20
+ this . #currentBinary$ = new StateCache ( async ( ) => {
21
+ const name : string = this . #selectedBinaryName. getValue ( )
22
+ const versionCache = this . #cliVersionsProvider. get ( name )
23
+ if ( versionCache == null ) return null
24
+ return await versionCache . getValue ( )
25
+ } )
39
26
40
- this . #selectedBinaryName = new BehaviorSubject < string > ( settings . getSettings ( ) . flowCommand )
27
+ // Bind the selected binary to the settings
41
28
this . #settings. watch$ ( config => config . flowCommand ) . subscribe ( ( flowCommand ) => {
42
29
this . #selectedBinaryName. next ( flowCommand )
43
30
} )
44
31
45
- this . #availableBinaries = KNOWN_BINS . reduce < AvailableBinariesCache > ( ( acc , bin ) => {
46
- acc [ bin ] = new StateCache ( async ( ) => await this . #fetchBinaryInformation( bin ) )
47
- acc [ bin ] . subscribe ( ( ) => {
48
- this . #availableBinaries$. invalidate ( )
49
- } )
50
- return acc
51
- } , { } )
52
-
53
- this . #availableBinaries$ = new StateCache ( async ( ) => {
54
- return await this . getAvailableBinaries ( )
55
- } )
56
-
57
- this . #currentBinary$ = new StateCache ( async ( ) => {
58
- const name : string = this . #selectedBinaryName. getValue ( )
59
- return await this . #availableBinaries[ name ] . getValue ( )
60
- } )
61
-
62
32
// Display warning to user if binary doesn't exist (only if not using the default binary)
63
- this . # currentBinary$. subscribe ( ( binary ) => {
64
- if ( binary === null && this . #selectedBinaryName. getValue ( ) !== 'flow' ) {
33
+ this . currentBinary$ . subscribe ( ( binary ) => {
34
+ if ( binary === null && this . #selectedBinaryName. getValue ( ) !== KNOWN_FLOW_COMMANDS . DEFAULT ) {
65
35
void vscode . window . showErrorMessage ( `The configured Flow CLI binary "${ this . #selectedBinaryName. getValue ( ) } " does not exist. Please check your settings.` )
66
36
}
67
37
} )
@@ -72,119 +42,43 @@ export class CliProvider {
72
42
#watchForBinaryChanges ( ) : void {
73
43
// Subscribe to changes in the selected binary to update the caches
74
44
this . #selectedBinaryName. pipe ( distinctUntilChanged ( ) , startWith ( null ) , pairwise ( ) ) . subscribe ( ( [ prev , curr ] ) => {
75
- // Swap out the cache for the selected binary
76
- if ( prev != null && ! KNOWN_BINS . includes ( prev ) ) {
77
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
78
- delete this . #availableBinaries[ prev ]
79
- }
80
- if ( curr != null && ! KNOWN_BINS . includes ( curr ) ) {
81
- this . #availableBinaries[ curr ] = new StateCache ( async ( ) => await this . #fetchBinaryInformation( curr ) )
82
- this . #availableBinaries[ curr ] . subscribe ( ( ) => {
83
- this . #availableBinaries$. invalidate ( )
84
- } )
85
- }
45
+ // Remove the previous binary from the cache
46
+ if ( prev != null ) this . #cliVersionsProvider. remove ( prev )
47
+
48
+ // Add the current binary to the cache
49
+ if ( curr != null ) this . #cliVersionsProvider. add ( curr )
86
50
87
51
// Invalidate the current binary cache
88
52
this . #currentBinary$. invalidate ( )
89
-
90
- // Invalidate the available binaries cache
91
- this . #availableBinaries$. invalidate ( )
92
53
} )
93
54
}
94
55
95
- // Fetches the binary information for the given binary
96
- async #fetchBinaryInformation ( bin : string ) : Promise < CliBinary | null > {
97
- try {
98
- // Get user's version informaton
99
- const buffer : string = ( await execDefault ( CHECK_FLOW_CLI_CMD (
100
- bin
101
- ) ) ) . stdout
102
-
103
- // Format version string from output
104
- const versionInfo : FlowVersionOutput = JSON . parse ( buffer )
105
-
106
- // Ensure user has a compatible version number installed
107
- const version : semver . SemVer | null = semver . parse ( versionInfo . version )
108
- if ( version === null ) return null
109
-
110
- return { name : bin , version }
111
- } catch {
112
- // Fallback to old method if JSON is not supported/fails
113
- return await this . #fetchBinaryInformationOld( bin )
114
- }
115
- }
116
-
117
- // Old version of fetchBinaryInformation (before JSON was supported)
118
- // Used as fallback for old CLI versions
119
- async #fetchBinaryInformationOld ( bin : string ) : Promise < CliBinary | null > {
120
- try {
121
- // Get user's version informaton
122
- const output = ( await execDefault ( CHECK_FLOW_CLI_CMD_NO_JSON (
123
- bin
124
- ) ) )
125
-
126
- let versionStr : string | null = parseFlowCliVersion ( output . stdout )
127
- if ( versionStr === null ) {
128
- // Try to fallback to stderr as patch for bugged version
129
- versionStr = parseFlowCliVersion ( output . stderr )
130
- }
131
-
132
- versionStr = versionStr != null ? semver . clean ( versionStr ) : null
133
- if ( versionStr === null ) return null
134
-
135
- // Ensure user has a compatible version number installed
136
- const version : semver . SemVer | null = semver . parse ( versionStr )
137
- if ( version === null ) return null
138
-
139
- return { name : bin , version }
140
- } catch {
141
- return null
142
- }
143
- }
144
-
145
- refresh ( ) : void {
146
- for ( const bin in this . #availableBinaries) {
147
- this . #availableBinaries[ bin ] . invalidate ( )
148
- }
149
- this . #currentBinary$. invalidate ( )
150
- }
151
-
152
- get availableBinaries$ ( ) : Observable < CliBinary [ ] > {
153
- return new Observable ( ( subscriber ) => {
154
- this . #availableBinaries$. subscribe ( ( binaries ) => {
155
- subscriber . next ( binaries )
156
- } )
157
- } ) . pipe ( distinctUntilChanged ( isEqual ) )
56
+ async getCurrentBinary ( ) : Promise < CliBinary | null > {
57
+ return await this . #currentBinary$. getValue ( )
158
58
}
159
59
160
- async getAvailableBinaries ( ) : Promise < CliBinary [ ] > {
161
- const bins : CliBinary [ ] = [ ]
162
- for ( const name in this . #availableBinaries) {
163
- const binary = await this . #availableBinaries[ name ] . getValue ( ) . catch ( ( ) => null )
164
- if ( binary !== null ) {
165
- bins . push ( binary )
166
- }
60
+ async setCurrentBinary ( name : string ) : Promise < void > {
61
+ if ( vscode . workspace . workspaceFolders == null ) {
62
+ await this . #settings. updateSettings ( { flowCommand : name } , vscode . ConfigurationTarget . Global )
63
+ } else {
64
+ await this . #settings. updateSettings ( { flowCommand : name } )
167
65
}
168
- return bins
169
66
}
170
67
171
68
get currentBinary$ ( ) : Observable < CliBinary | null > {
172
69
return this . #currentBinary$. pipe ( distinctUntilChanged ( isEqual ) )
173
70
}
174
71
175
- async getCurrentBinary ( ) : Promise < CliBinary | null > {
176
- return await this . #currentBinary$ . getValue ( )
72
+ async getBinaryVersions ( ) : Promise < CliBinary [ ] > {
73
+ return await this . #cliVersionsProvider . getVersions ( )
177
74
}
178
75
179
- async setCurrentBinary ( name : string ) : Promise < void > {
180
- await this . #settings . updateSettings ( { flowCommand : name } )
76
+ get binaryVersions$ ( ) : Observable < CliBinary [ ] > {
77
+ return this . #cliVersionsProvider . versions$ . pipe ( distinctUntilChanged ( isEqual ) )
181
78
}
182
- }
183
-
184
- export function isCadenceV1Cli ( version : semver . SemVer ) : boolean {
185
- return CADENCE_V1_CLI_REGEX . test ( version . raw )
186
- }
187
79
188
- export function parseFlowCliVersion ( buffer : Buffer | string ) : string | null {
189
- return buffer . toString ( ) . match ( LEGACY_VERSION_REGEXP ) ?. [ 1 ] ?? null
80
+ // Refresh all cached binary versions
81
+ refresh ( ) : void {
82
+ this . #cliVersionsProvider. refresh ( )
83
+ }
190
84
}
0 commit comments