Passed
Push — develop ( 80ffe5...0e1817 )
by Bjarn
01:22 queued 11s
created

InstallController.installTools   A

Complexity

Conditions 2

Size

Total Lines 23
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
dl 0
loc 23
rs 9.4
c 0
b 0
f 0
1
import * as fs from 'fs'
2
import inquirer, {Answers} from 'inquirer'
3
import {white} from 'kleur/colors'
4
import {Listr, ListrTask} from 'listr2'
5
import {Config, Database} from '../models/config'
6
import Dnsmasq from '../services/dnsmasq'
7
import Mailhog from '../services/mailhog'
8
import Nginx from '../services/nginx'
9
import {clearConsole} from '../utils/console'
10
import {getDatabaseByName} from '../utils/database'
11
import {ensureDirectoryExists} from '../utils/filesystem'
12
import {getOptionalServiceByname} from '../utils/optionalService'
13
import {client} from '../utils/os'
14
import {getPhpFpmByName} from '../utils/phpFpm'
15
import {ensureHomeDirExists, jaleConfigPath, jaleLogsPath} from '../utils/jale'
16
import {requireSudo} from '../utils/sudo'
17
import {getToolByName} from '../utils/tools'
18
19
class InstallController {
20
21
    private readonly questions = [
22
        {
23
            type: 'input',
24
            name: 'domain',
25
            message: 'Enter a domain',
26
            default: 'test',
27
            validate: (input: string) => {
28
                return input !== ''
29
            }
30
        },
31
        {
32
            type: 'checkbox',
33
            name: 'phpVersions',
34
            message: 'Choose one or more PHP versions',
35
            choices: ['[email protected]', '[email protected]', '[email protected]', '[email protected]', '[email protected]'],
36
            validate: (input: string[]) => {
37
                return input.length >= 1
38
            }
39
        },
40
        {
41
            type: 'list',
42
            name: 'database',
43
            message: 'Choose a database',
44
            choices: ['[email protected]', '[email protected]', 'mariadb'],
45
            validate: (input: string[]) => {
46
                return input.length >= 1
47
            }
48
        },
49
        {
50
            type: 'checkbox',
51
            name: 'optionalServices',
52
            message: 'Optional services',
53
            choices: ['redis', 'elasticsearch']
54
        },
55
        {
56
            type: 'checkbox',
57
            name: 'apps',
58
            message: 'Tools and apps',
59
            choices: ['wp-cli', 'magerun', 'magerun2', 'drush']
60
        }
61
    ]
62
63
    /**
64
     * Execute the installation process.
65
     */
66
    execute = async (): Promise<boolean> => {
67
        clearConsole()
68
        console.log(white('✨ Thanks for using Jale! Let\'s get you started quickly.\n'))
69
70
        await requireSudo()
71
72
        inquirer
73
            .prompt(this.questions)
74
            .then(answers => {
75
                this.install(answers)
76
            })
77
            .catch(error => {
78
                console.log('Something went wrong. However, this version is just a proof of concept and the error handling sucks. Sorry, again.')
79
            })
80
81
        return true
82
    }
83
84
    /**
85
     * Start the installation of Jale.
86
     *
87
     * @param answers
88
     * @private
89
     */
90
    private async install(answers: any) {
91
        await ensureHomeDirExists()
92
        await ensureDirectoryExists(jaleLogsPath)
93
94
        const tasks = new Listr([
95
            this.configureJale(answers),
96
            this.installDnsMasq(),
97
            this.installNginx(),
98
            this.installMailhog(),
99
            {
100
                title: 'Install PHP-FPM',
101
                task: (ctx, task): Listr =>
102
                    task.newListr(
103
                        this.installPhpFpm(answers.phpVersions)
104
                    )
105
            },
106
            this.installDatabase(answers.database),
107
            this.installOptionalServices(answers),
108
            this.installTools(answers)
109
        ])
110
111
        try {
112
            // We're all set. Let's configure Jale.
113
            await tasks.run()
114
            console.log(`\n✨ Successfully installed Jale, Just Another Local Environment! ✅\n`)
115
        } catch (e) {
116
            console.error(e)
117
        }
118
    }
119
120
    /**
121
     * Configure Jale by parsing the answers and creating a configuration file.
122
     *
123
     * @param answers
124
     * @private
125
     */
126
    private configureJale = (answers: any): ListrTask => ({
127
        title: 'Configure Jale',
128
        task: (ctx, task): void => {
129
            let config = <Config>{
130
                domain: answers.domain,
131
                database: <Database>{password: 'root'},
132
                services: null // TODO: Make services configurable.
133
            }
134
135
            return fs.writeFileSync(jaleConfigPath, JSON.stringify(config, null, 2))
136
        }
137
    })
138
139
140
    //
141
    // Service installation functions
142
    //
143
144
    private installDnsMasq = (): ListrTask => ({
145
        title: 'Install Dnsmasq',
146
        task: (ctx, task): Listr =>
147
            task.newListr([
148
                {
149
                    title: 'Installing DnsMasq',
150
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
151
                    skip: async (ctx): Promise<string | boolean> => {
152
                        const isInstalled = await client().packageManager.packageIsInstalled('dnsmasq')
153
154
                        if (isInstalled) return 'Dnsmasq is already installed.'
155
                    },
156
                    task: (new Dnsmasq).install
157
                },
158
                {
159
                    title: 'Configure DnsMasq',
160
                    task: (new Dnsmasq).configure
161
                },
162
                {
163
                    title: 'Restart DnsMasq',
164
                    task: (new Dnsmasq).restart
165
                }
166
            ])
167
    })
168
169
    private installNginx = (): ListrTask => ({
170
        title: 'Install Nginx',
171
        task: (ctx, task): Listr =>
172
            task.newListr([
173
                {
174
                    title: 'Installing Nginx',
175
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
176
                    skip: async (ctx): Promise<string | boolean> => {
177
                        const isInstalled = await client().packageManager.packageIsInstalled('nginx')
178
179
                        if (isInstalled) return 'Nginx is already installed.'
180
                    },
181
                    task: (new Nginx).install
182
                },
183
                {
184
                    title: 'Configure Nginx',
185
                    task: (new Nginx).configure
186
                },
187
                {
188
                    title: 'Restart Nginx',
189
                    task: (new Nginx).restart
190
                }
191
            ])
192
    })
193
194
    private installPhpFpm = (phpVersions: string[]): ListrTask[] => {
195
        let phpInstallTasks: ListrTask[] = []
196
197
        phpVersions.forEach((phpVersion: string, index) => {
198
            phpInstallTasks.push({
199
                title: `Install ${phpVersion}`,
200
                task: (ctx, task): Listr =>
201
                    task.newListr([
202
                        {
203
                            title: `Installing ${phpVersion}`,
204
                            // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
205
                            skip: async (ctx): Promise<string | boolean> => {
206
                                if (phpVersion == '[email protected]') phpVersion = 'php'
207
                                const isInstalled = await client().packageManager.packageIsInstalled(phpVersion)
208
209
                                if (isInstalled) return `${phpVersion} is already installed.`
210
                            },
211
                            task: (getPhpFpmByName(phpVersion)).install
212
                        },
213
                        {
214
                            title: `Configure ${phpVersion}`,
215
                            task: (getPhpFpmByName(phpVersion)).configure
216
                        },
217
                        {
218
                            title: `Link ${phpVersion}`,
219
                            enabled: (): boolean => index === 0,
220
                            task: (getPhpFpmByName(phpVersion)).linkPhpVersion
221
                        },
222
                        {
223
                            title: `Restart ${phpVersion}`,
224
                            enabled: (): boolean => index === 0,
225
                            task: (getPhpFpmByName(phpVersion)).restart
226
                        },
227
                        {
228
                            title: `Stop ${phpVersion}`,
229
                            enabled: (): boolean => index !== 0,
230
                            task: (getPhpFpmByName(phpVersion)).stop
231
                        }
232
                    ])
233
            })
234
        })
235
236
        return phpInstallTasks
237
    }
238
239
    private installDatabase = (database: string): ListrTask => ({
240
        title: 'Install Database',
241
        task: (ctx, task): Listr =>
242
            task.newListr([
243
                {
244
                    title: `Installing ${database}`,
245
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
246
                    skip: async (ctx): Promise<string | boolean> => {
247
                        const isInstalled = await client().packageManager.packageIsInstalled(database)
248
249
                        if (isInstalled) return `${database} is already installed.`
250
                    },
251
                    task: (getDatabaseByName(database)).install
252
                },
253
                {
254
                    title: 'Configure ${database}',
255
                    task: (getDatabaseByName(database)).configure
256
                },
257
                {
258
                    title: 'Restart ${database}',
259
                    task: (getDatabaseByName(database)).restart
260
                }
261
            ])
262
    })
263
264
    // TODO: make Mailhog configurable. Currently required due to php config which has mailhog set for sendmail.
265
    private installMailhog = (): ListrTask => ({
266
        title: 'Install Mailhog',
267
        task: (ctx, task): Listr =>
268
            task.newListr([
269
                {
270
                    title: `Installing Mailhog`,
271
                    // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
272
                    skip: async (ctx): Promise<string | boolean> => {
273
                        const isInstalled = await client().packageManager.packageIsInstalled('mailhog')
274
275
                        if (isInstalled) return `Mailhog is already installed.`
276
                    },
277
                    task: (new Mailhog).install
278
                },
279
                {
280
                    title: 'Configure Mailhog',
281
                    task: (new Mailhog).configure
282
                },
283
                {
284
                    title: 'Restart Mailhog',
285
                    task: (new Mailhog).restart
286
                }
287
            ])
288
    })
289
290
    private installOptionalServices = (answers: Answers): ListrTask => {
291
        let optionalServicesTasks: ListrTask[] = []
292
293
        answers.optionalServices.forEach((serviceName: string) => {
294
            const service = getOptionalServiceByname(serviceName)
295
            optionalServicesTasks.push({
296
                title: `Install ${service.service}`,
297
                task: (ctx, task): Listr =>
298
                    task.newListr([
299
                        {
300
                            title: `Installing ${service.service}`,
301
                            // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
302
                            skip: async (ctx): Promise<string | boolean> => {
303
                                const isInstalled = await client().packageManager.packageIsInstalled(service.service)
304
305
                                if (isInstalled) return `${service.service} is already installed.`
306
                            },
307
                            task: service.install
308
                        },
309
                        {
310
                            title: `Configure ${service.service}`,
311
                            task: service.configure
312
                        },
313
                        {
314
                            title: `Restart ${service.service}`,
315
                            task: service.restart
316
                        }
317
                    ])
318
            })
319
        })
320
321
        return {
322
            title: 'Install Optional Services',
323
            task: (ctx, task): Listr =>
324
                task.newListr(optionalServicesTasks)
325
        }
326
    }
327
328
    private installTools = (answers: Answers): ListrTask => {
329
        let toolsTasks: ListrTask[] = []
330
331
        answers.apps.forEach((toolName: string) => {
332
            const tool = getToolByName(toolName)
333
            toolsTasks.push({
334
                title: `Install ${tool.name}`,
335
                // @ts-ignore this is valid, however, the types are kind of a mess? not sure yet.
336
                skip: async (ctx): Promise<string | boolean> => {
337
                    const isInstalled = await tool.isInstalled()
338
339
                    if (isInstalled) return `${tool.name} is already installed.`
340
                },
341
                task: tool.install
342
            })
343
        })
344
345
        return {
346
            title: 'Install Tools and Apps',
347
            task: (ctx, task): Listr =>
348
                task.newListr(
349
                    toolsTasks,
350
                    {concurrent: false}
351
                )
352
        }
353
    }
354
}
355
356
export default InstallController