/
dependency-installer.ts
131 lines (114 loc) · 5.04 KB
/
dependency-installer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { window } from 'vscode'
import { InstallFlowCLI } from './installers/flow-cli-installer'
import { Installer, InstallerConstructor, InstallerContext, InstallError } from './installer'
import { promptUserErrorMessage } from '../ui/prompts'
import { StateCache } from '../utils/state-cache'
import { LanguageServerAPI } from '../server/language-server'
import { CliProvider } from '../flow-cli/cli-provider'
const INSTALLERS: InstallerConstructor[] = [
InstallFlowCLI
]
export class DependencyInstaller {
registeredInstallers: Installer[] = []
missingDependencies: StateCache<Installer[]>
#installerContext: InstallerContext
constructor (languageServerApi: LanguageServerAPI, cliProvider: CliProvider) {
this.#installerContext = {
refreshDependencies: this.checkDependencies.bind(this),
languageServerApi,
cliProvider
}
// Register installers
this.#registerInstallers()
// Create state cache for missing dependencies
this.missingDependencies = new StateCache(async () => {
const missing: Installer[] = []
for (const installer of this.registeredInstallers) {
if (!(await installer.verifyInstall())) {
missing.push(installer)
}
}
return missing
})
// Display error message if dependencies are missing
this.missingDependencies.subscribe((missing: Installer[]) => {
if (missing.length !== 0) {
// Prompt user to install missing dependencies
promptUserErrorMessage(
'Not all dependencies are installed: ' + missing.map(x => x.getName()).join(', '),
[
{
label: 'Install Missing Dependencies',
callback: () => { void this.#installMissingDependencies() }
}
]
)
}
})
}
async checkDependencies (): Promise<void> {
// Invalidate and wait for state to update
// This will trigger the missingDependencies subscriptions
this.missingDependencies.invalidate()
await this.missingDependencies.getValue()
}
async installMissing (): Promise<void> {
await this.#installMissingDependencies()
}
#registerInstallers (): void {
// Recursively register installers and their dependencies in the correct order
(function registerInstallers (this: DependencyInstaller, installers: InstallerConstructor[]) {
installers.forEach((_installer) => {
// Check if installer is already registered
if (this.registeredInstallers.find(x => x instanceof _installer) != null) { return }
// Register installer and dependencies
const installer = new _installer(this.#installerContext)
registerInstallers.bind(this)(installer.dependencies)
this.registeredInstallers.push(installer)
})
}).bind(this)(INSTALLERS)
}
async #installMissingDependencies (): Promise<void> {
const missing = await this.missingDependencies.getValue()
const installed: Installer[] = this.registeredInstallers.filter(x => !missing.includes(x))
await new Promise<void>((resolve) => {
setTimeout(() => { resolve() }, 2000)
})
for (const installer of missing) {
try {
// Check if dependencies are installed
const missingDeps = installer.dependencies
.filter(x => installed.find(y => y instanceof x) == null)
.map(x => this.registeredInstallers.find(y => y instanceof x))
// Show error if dependencies are missing
if (missingDeps.length !== 0) {
throw new InstallError('Cannot install ' + installer.getName() + '. Missing depdenencies: ' + missingDeps.map(x => x?.getName()).join(', '))
}
await installer.runInstall()
installed.push(installer)
} catch (err) {
if (err instanceof InstallError) {
void window.showErrorMessage(err.message)
}
}
}
// Refresh missing dependencies
this.missingDependencies.invalidate()
const failed = await this.missingDependencies.getValue()
if (failed.length !== 0) {
// Find all failed installations
void window.showErrorMessage('Failed to install all dependencies. The following may need to be installed manually: ' + failed.map(x => x.getName()).join(', '))
} else {
if (process.platform === 'win32') {
// Windows requires a restart of VSCode to refresh environment variables
void window.showInformationMessage('All dependencies installed successfully. Newly installed dependencies will not be available in terminals until VSCode is restarted.')
} else if (process.platform !== 'darwin') {
// Linux requires a fresh login to refresh environment variables for new terminals
void window.showInformationMessage('All dependencies installed successfully. Newly installed dependencies will not be available in terminals until you log out and back in.')
} else {
// MacOS requires a restart of active terminals to refresh environment variables
void window.showInformationMessage('All dependencies installed successfully. You may need to restart active terminals.')
}
}
}
}