src/controllers/installController.ts   A
last analyzed

Complexity

Total Complexity 19
Complexity/F 1.9

Size

Lines of Code 380
Function Count 10

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 19
eloc 277
mnd 9
bc 9
fnc 10
dl 0
loc 380
rs 10
bpm 0.9
cpm 1.9
noi 0
c 0
b 0
f 0

10 Functions

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