UNPKG

kui-shell

Version:

This is the monorepo for Kui, the hybrid command-line/GUI electron-based Kubernetes tool

313 lines (273 loc) 9.15 kB
/* * Copyright 2018 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const debug = require('debug')('builder/configure') const path = require('path') const fs = require('fs-extra') const colors = require('colors/safe') const { moduleExists } = require('./module-exists') const parseOptions = require('./parse-options') /** * Tell the user where we're at * */ const task = taskName => console.log(colors.dim('task: ') + taskName) const info = (key, value) => console.log(colors.blue(`${key}: `) + value) /** * Read in index.html * */ const readIndex = options => new Promise((resolve, reject) => { const { templateDir } = options.build debug('read template', templateDir) task(`read index.html template from ${templateDir}`) fs.readFile(path.join(templateDir, 'index.html'), (err, data) => { if (err) { reject(err) } else { resolve(data.toString()) } }) }) /** * Unify the given settings of configurations * */ const unify = (setting1, setting2) => { const uniqe = arr => { return arr.filter((element, index) => arr.indexOf(element) === index) } const unifiedSettings = setting1 for (const key in setting2) { if (!unifiedSettings[key]) { unifiedSettings[key] = setting2[key] } else { if (Array.isArray(unifiedSettings[key]) && Array.isArray(setting2[key])) { // concatenate the arrays unifiedSettings[key] = unifiedSettings[key].concat(setting2[key]) // remove the duplicated elements and join the array into a string unifiedSettings[key] = uniqe(unifiedSettings[key]).join(' ') } else if (!Array.isArray(unifiedSettings[key]) && Array.isArray(setting2[key])) { // split the first string into an array and concatenate the arrays unifiedSettings[key] = unifiedSettings[key].split(' ').concat(setting2[key]) // remove the duplicated elements and join the array into a string unifiedSettings[key] = uniqe(unifiedSettings[key]).join(' ') } else if (unifiedSettings[key] !== setting2[key]) { throw new Error(`collision key ${key} ${setting1[key]} ${setting2[key]} in settings`) } } } return unifiedSettings } /** * Evaluate macros in the given string, using the given setting of configurations * */ const evaluateMacros = settings => str => { debug('evaluateMacros', settings) task('evaluate macros', str) for (const key in settings) { str = str.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), settings[key]) } return str } /** * Inject custom CSS * */ const injectCSS = (env, settings, marker = '<head>') => str => { debug('injectCSS', settings) task(`injectCSS`, str) if (settings) { const format = css => { // only inject test css if we are running a test if (!/^_test/.test(css) || process.env.RUNNING_KUI_TEST !== undefined) { const href = /^http/.test(css) ? css : `${env.cssHome}${css}` return `<link href="${href}" rel="stylesheet" type="text/css">` } else { return '' } } const css = Array.isArray(settings) ? settings.reduce((links, css) => { return `${links}\n${format(css)}` }, '') : format(settings) return str.replace(marker, `${marker}${css}`) } else { return str } } /** * Write the updated index.html * */ const writeIndex = settings => str => new Promise((resolve, reject) => { if (settings.build.writeIndex === false) { return resolve() } const indexHtml = `index${settings.env.nameSuffix || ''}.html` task(`write ${indexHtml} to ${settings.build.buildDir}`) fs.writeFile(path.join(settings.build.buildDir, indexHtml), str, err => { if (err) { reject(err) } else { resolve() } }) }) /** * Stash the chosen configuration settings to the buildDir, and update * the app/package.json so that the productName field reflects the * chosen setting * */ const writeConfig = settings => new Promise((resolve, reject) => { if (settings.build.writeConfig === false) { return resolve() } const { configDir } = settings.build task(`write config to ${configDir}`) const config = Object.assign({}, settings) delete config.build debug('writeConfig', configDir, config) fs.writeFile(path.join(configDir, 'config.json'), JSON.stringify(config, undefined, 4), err => { if (err) { reject(err) } else { task('write package.json') const packageAppPjson = path.join(configDir, '../package.json') const topLevel = moduleExists(packageAppPjson) ? require(packageAppPjson) : require(path.join(process.env.CLIENT_HOME, 'package.json')) const packageJson = Object.assign({}, topLevel, config, { name: '@kui-shell/settings' }) fs.writeFile(path.join(configDir, 'package.json'), JSON.stringify(packageJson, undefined, 4), err => { if (err) { reject(err) } else { resolve() } }) } }) }) /** * Do a build * */ const doBuild = settings => () => Promise.all([ writeConfig(settings), readIndex(settings) .then(evaluateMacros(unify(settings.env, settings.theme))) .then(injectCSS(settings.env, settings.theme.css)) .then(injectCSS(settings.env, settings.theme.cssOverrides, '<!-- css overrides go here -->')) .then(writeIndex(settings)) ]) /** * Either project out a field of the configuration settings, or do a build * */ const doWork = settings => { info('env.main', settings.env.main) info('env.cssHome', settings.env.cssHome) info('env.imageHome', settings.env.imageHome) info('theme', settings.theme.cssTheme) info('buildDir', settings.build.buildDir) return Promise.all([fs.mkdirp(settings.build.buildDir), fs.mkdirp(settings.build.configDir)]) .then(doBuild(settings)) .then(() => colors.green('ok:') + ' build successful') } /** * Build index.html * */ const main = (env, overrides = {}) => { return parseOptions(env, overrides) // read options .then(settings => doWork(settings)) .then(console.log) .catch(err => { console.error(err) process.exit(1) }) } /** * Load the overrides from the KUI_BUILD_CONFIG or the default overrides location * * @param programmaticOverrides if we are being invoked programmatically, such as * from webpack.config.js, then this gives the caller the option to * specify some overrides via that programmatic call path * */ const loadOverrides = (programmaticOverrides = {}) => { let overrideDirectory = process.env.KUI_BUILD_CONFIG && path.resolve(process.env.KUI_BUILD_CONFIG) if (!overrideDirectory || !fs.existsSync(overrideDirectory)) { overrideDirectory = process.env.CLIENT_HOME && path.resolve(path.join(process.env.CLIENT_HOME, 'theme')) if (!overrideDirectory || !fs.existsSync(overrideDirectory)) { overrideDirectory = path.resolve(path.join(process.cwd(), 'theme')) } } info('theme directory', overrideDirectory) const loadOverride = file => { try { if (overrideDirectory) { debug(`Using this override directory: ${overrideDirectory}`) return require(path.join(overrideDirectory, file)) } else { return {} } } catch (err) { debug('error in loadOverride', err) return {} } } const userEnv = loadOverride('env') const userTheme = loadOverride('theme') const userConfig = loadOverride('config') const overrides = { build: programmaticOverrides.build || {}, env: Object.assign({}, userEnv, programmaticOverrides.env), theme: Object.assign({}, userTheme, programmaticOverrides.theme), config: Object.assign({}, userConfig, programmaticOverrides.config) } if ( process.env.KUI_STAGE && (!programmaticOverrides || !programmaticOverrides.build || !programmaticOverrides.build.buildDir) ) { overrides.build.buildDir = path.join(process.env.KUI_STAGE, 'node_modules/@kui-shell/build') overrides.build.configDir = path.join(process.env.KUI_STAGE, 'node_modules/@kui-shell/settings') } debug('overrides', overrides) return overrides } if (require.main === module) { const env = process.argv[2] || 'standalone' debug('called directly', env) main(env, loadOverrides()) } else { debug('required as a module') class Builder { /** @param env is one of the entries in ../defaults/envs */ build(env, overrides) { return main(env, loadOverrides(overrides)) } } module.exports = Builder }