UNPKG

6.22 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2017, Shopgate, Inc. All rights reserved.
3 *
4 * This source code is licensed under the Apache 2.0 license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7const fsEx = require('fs-extra')
8const path = require('path')
9const inquirer = require('inquirer')
10const logger = require('../logger')
11const UserSettings = require('../user/UserSettings')
12const AppSettings = require('../app/AppSettings')
13const FrontendProcess = require('../app/frontend/FrontendProcess')
14const FrontendSetup = require('../app/frontend/FrontendSetup')
15const DcHttpClient = require('../DcHttpClient')
16const utils = require('../utils/utils')
17const ExtensionConfigWatcher = require('../app/ExtensionConfigWatcher')
18
19const { SETTINGS_FOLDER, THEMES_FOLDER } = require('../app/Constants')
20
21/**
22 * The FrontendAction class.
23 */
24class FrontendAction {
25 constructor () {
26 this.appSettings = new AppSettings().validate()
27 this.userSettings = new UserSettings().validate()
28 this.settingsFolder = path.join(this.appSettings.getApplicationFolder(), SETTINGS_FOLDER)
29 this.dcClient = new DcHttpClient(this.userSettings, logger)
30 this.frontendSetup = new FrontendSetup(this.dcClient, this.appSettings)
31 this.extensionConfigWatcher = new ExtensionConfigWatcher(this.appSettings)
32 }
33
34 /**
35 * Registers the frontend command.
36 * @param {Command} caporal Instance of the commander module.
37 */
38 static register (caporal) {
39 caporal
40 .command('frontend start')
41 .description('Starts the webpack dev server for the frontend development')
42 .option('-t, --theme [value]', 'The name of the theme that you want to be compiled by webpack.')
43 .option('-h, --host [value]', 'The URL or IP address of your local development environment.')
44 .option('-p, --port [value]', 'The port to use for accessing the output via browser.')
45 .action((args, options) => { new FrontendAction().run('start', args, options) })
46
47 caporal
48 .command('frontend setup')
49 .description('Changes the settings for the frontend development')
50 .action((args, options) => { new FrontendAction().run('setup', args, options) })
51 }
52
53 /**
54 * Find all themes inside the project.
55 * @returns {Array}
56 */
57 async findThemes () {
58 // Absolute path to the themes.
59 const source = path.resolve(this.appSettings.getApplicationFolder(), THEMES_FOLDER)
60
61 // Get all folders inside the themes directory.
62 const folders = await fsEx.readdir(source)
63
64 const promises = folders.map(folder => fsEx.lstat(path.join(source, folder)))
65 const results = await Promise.all(promises)
66
67 return folders.filter((folder, index) => results[index].isDirectory())
68 }
69
70 /**
71 * Inquires a theme selection if not theme was set as an option.
72 * @return {Promise}
73 */
74 requestThemeOption () {
75 return new Promise(async (resolve, reject) => {
76 const themes = await this.findThemes()
77 if (themes.length === 1) return resolve(themes[0])
78
79 inquirer
80 .prompt([{
81 type: 'list',
82 name: 'theme',
83 message: 'Please choose a theme to use',
84 choices: themes
85 }])
86 .then(answers => resolve(answers.theme))
87 .catch(error => reject(error))
88 })
89 }
90
91 /**
92 * Runs the frontend process.
93 * @param {string} action The action to perform.
94 * @param {Object} [args={}] The process args.
95 * @param {Object} [options={}] The process options.
96 */
97 async run (action, args, options = {}) {
98 this.userSettings = new UserSettings().validate()
99 this.appSettings = new AppSettings().validate()
100
101 const pid = await utils.previousProcess('frontend', this.settingsFolder)
102 if (pid) throw new Error(`Frontend process is already running with pid: ${pid}. Please quit this process first.`)
103
104 switch (action) {
105 default:
106 case 'start': {
107 utils.generateComponentsJson(this.appSettings)
108 let theme = options.theme
109 if (!theme) theme = await this.requestThemeOption()
110 await this.start({...options, theme}, await this.buildThemePath(theme))
111 break
112 }
113 case 'setup': {
114 await this.frontendSetup.run()
115 break
116 }
117 }
118
119 process.on('SIGINT', async () => {
120 await this.extensionConfigWatcher.stop()
121 await utils.deleteProcessFile('frontend', this.settingsFolder)
122 })
123 }
124
125 /**
126 * Builds the theme folder path.
127 * @param {string} theme The theme folder.
128 * @return {string}
129 */
130 async buildThemePath (theme) {
131 const themePath = path.join(this.appSettings.getApplicationFolder(), THEMES_FOLDER, theme)
132 if (!await fsEx.exists(themePath)) {
133 throw new Error(`Can't find theme '${theme}'. Please make sure you passed the right theme.`)
134 }
135 return themePath
136 }
137
138 /**
139 * Runs the 'start' command.
140 * @param {Object} options The process options.
141 * @param {string} themeFolder The theme folder.
142 */
143 async start (options, themeFolder) {
144 const frontend = new FrontendProcess(options, this.frontendSetup, this.appSettings)
145
146 await this.extensionConfigWatcher.start()
147 this.extensionConfigWatcher.on('configChange', (config) => {
148 // Create components.json if needed
149 utils.generateComponentsJson(this.appSettings)
150 })
151
152 await this.updateThemeConfig(themeFolder)
153 await frontend.run()
154 await utils.setProcessFile('frontend', this.settingsFolder, process.pid)
155 }
156
157 async updateThemeConfig (templateFolder) {
158 logger.info(`Generating theme config`)
159 const extensionConfigFile = await fsEx.readJSON(path.join(templateFolder, 'extension-config.json'))
160
161 return this.dcClient.generateExtensionConfig(extensionConfigFile, this.appSettings.getId())
162 .then((extConfig) => {
163 if (!extConfig.frontend) return logger.warn('No config with the destination \'frontend\' found')
164 const appJsonFile = path.join(templateFolder, 'config', 'app.json')
165 return fsEx.outputJson(appJsonFile, extConfig.frontend, {spaces: 2})
166 })
167 .then(() => {
168 logger.info(`Updated theme config`)
169 })
170 .catch(err => {
171 throw new Error('Could not generate config: ' + err.message)
172 })
173 }
174}
175
176module.exports = FrontendAction