UNPKG

fusox

Version:

Command line wrapper for fuse-box

280 lines (225 loc) 11 kB
const minimist = require('minimist') const util = require('util') const dotenv = require('dotenv') const process = require('process') const path = require('path') const os = require('os') const _ = require('lodash') const cors = require('cors-anywhere') const {execSync} = require('child_process') const {existsSync} = require('fs') const {parseBoolean, parseNumber, printHeader, getFreePort} = require('../helpers') module.exports = {parseBuildArgs, parseBuildFlags, buildCommand} function loadEnvConfig (paths) { let configs = paths.map((path) => (dotenv.config({path: path}).parsed || {})) return _.merge({}, ...configs) } function parseBuildArgs (args) { let outerArgs = {...args} // convert arg1 arg2 to arg1:arg2 if (args._[0] && args._[0].indexOf(':') === -1) { args._ = [args._.join(':')] } return args._.map((entry) => _.merge({}, outerArgs, minimist(entry.split(':')))) } function parseBuildFlags (args) { return args.map((args) => { let source = args._.shift() let destination = args._.shift() if (!source) { throw new Error('Missing source files directory argument') } if (!destination) { throw new Error('Missing destination directory argument') } let mode = args.mode || parseBoolean(args.production) ? 'production' : 'development' let generateSourceMaps = args.sourcemaps === undefined ? mode === 'development' : parseBoolean(args.sourcemaps) let useCache = args.cache === undefined ? mode === 'development' : parseBoolean(args.cache) let runApplication = args.run === undefined ? false : parseBoolean(args.run) let defaultDevServerPort = args.port === undefined ? 9999 : parseNumber(args.port) let cleanArtifacts = args.clean === undefined ? false : parseBoolean(args.clean) let uglifyCode = args.uglify === undefined ? mode === 'production' : parseBoolean(args.uglify) let runCorsProxy = args.cors === undefined ? false : parseBoolean(args.cors) && mode === 'development' let defaultCorsProxyPort = 8888 let sourcePath = path.resolve(args.cwd || process.cwd(), (source || '').replace('~', os.homedir())) let resolveSourcePath = (...segments) => path.resolve(sourcePath, ...segments) let resolveSourcePathRelative = (path) => (path || '').replace(sourcePath + '/', '') let cwdPath = args.cwd ? path.resolve(args.cwd) : process.cwd() let resolveCwdPath = (...segments) => path.resolve(cwdPath, ...segments) let resolveSourceParentPath = (...segments) => path.resolve(sourcePath, '..', ...segments) let findExistingPath = (...paths) => paths.find((p) => existsSync(p)) let destinationPath = resolveCwdPath((destination || '').replace('~', os.homedir())) let resolveDestinationPath = (...segments) => path.resolve(destinationPath, ...segments) let nodeModulesPath = resolveCwdPath('node_modules') let customTsconfigPath = resolveCwdPath('tsconfig.json') let customTsconfigPath2 = resolveSourceParentPath('tsconfig.json') let defaultTsconfigPath = path.resolve(__dirname, '../assets/tsconfig.json') let tsconfigPath = findExistingPath(customTsconfigPath, customTsconfigPath2, defaultTsconfigPath) let customTailwindCssConfigPath = resolveCwdPath('tailwind.js') let customTailwindCssConfigPath2 = resolveSourceParentPath('tailwind.js') let defaultTailwindCssConfigPath = null let tailwindCssConfigPath = findExistingPath(customTailwindCssConfigPath, customTailwindCssConfigPath2, defaultTailwindCssConfigPath) if (tailwindCssConfigPath) { process.env.TAILWINDCSS_CONFIG_PATH = tailwindCssConfigPath } let customPostCssConfigPath = resolveCwdPath('postcss.js') let customPostCssConfigPath2 = resolveSourceParentPath('postcss.js') let defaultPostCssConfigPath = path.resolve(__dirname, '../assets/postcss.js') let postCssConfigPath = findExistingPath(customPostCssConfigPath, customPostCssConfigPath2, defaultPostCssConfigPath) let postCssConfig = require(postCssConfigPath) let watchForChanges = args.watch === undefined ? false : parseBoolean(args.watch) let parseEnvFiles = args.env === undefined ? true : parseBoolean(args.env) let envConfigPaths = [ resolveCwdPath('.env.dist'), resolveCwdPath('.env'), resolveCwdPath('.env.' + mode + '.dist'), resolveCwdPath('.env.' + mode), resolveSourceParentPath('.env.dist'), resolveSourceParentPath('.env'), resolveSourceParentPath('.env.' + mode + '.dist'), resolveSourceParentPath('.env.' + mode), resolveSourcePath('.env.dist'), resolveSourcePath('.env'), resolveSourcePath('.env.' + mode + '.dist'), resolveSourcePath('.env.' + mode), ] let envConfig = parseEnvFiles ? loadEnvConfig(envConfigPaths) : {} process.env.NODE_ENV = envConfig.NODE_ENV = mode let processEnvConfig = _.mapKeys( _.mapValues(envConfig, (v) => JSON.stringify(v)), (v, k) => 'process.env.' + k ) let packageJsonPath = resolveCwdPath('package.json') let packageJson = existsSync(packageJsonPath) && require(packageJsonPath) || null let globalName = (args.name && args.name.toString()) || null let appName = null let defaultAssetFilesExtensions = 'jpg,png,gif,svg,txt,html' let appendAssetFilesExtensions = (args.assets === undefined ? false : args.assets.toString().substr(0, 1) === '+') args.assets = appendAssetFilesExtensions === false ? args.assets : defaultAssetFilesExtensions + ',' + args.assets.toString().substr(1) let assetFilesExtensions = (args.assets === undefined ? defaultAssetFilesExtensions : args.assets).toString() .split(',').map((i) => util.format('*.%s', i.trim())) let defaultDynamicFilesExtensions = 'json,yml,txt' let appendDynamicFilesExtensions = (args.dynamic === undefined ? false : args.dynamic.toString().substr(0, 1) === '+') args.dynamic = appendDynamicFilesExtensions === false ? args.dynamic : defaultDynamicFilesExtensions + ',' + args.dynamic.toString().substr(1) let dynamicFilesInstructions = (args.dynamic === undefined ? defaultDynamicFilesExtensions : args.dynamic).toString() .split(',').map((i) => util.format('+**/*.%s', i.trim())).join(' ') let fuseboxCachePath = resolveCwdPath('.fusebox') process.env.FUSEBOX_TEMP_FOLDER = fuseboxCachePath if (mode !== 'development' && mode !== 'production') { throw new Error(util.format('Mode "%s" is not supported, supported modes are "development" and "production"', mode)) } if (!existsSync(sourcePath)) { throw new Error(util.format('Source directory "%s" does not exist', sourcePath)) } let browser = args.browser === undefined ? false : parseBoolean(args.browser) let server = args.server === undefined ? false : parseBoolean(args.server) let electron = args.electron === undefined ? false : parseBoolean(args.electron) let library = args.library === undefined ? false : parseBoolean(args.library) let target = args.target || browser && 'browser' || server && 'server' || electron && 'electron' || library && 'library' if (!target) { throw new Error('You must specify a build target using either the "--target=browser|server|electron|library", "--browser", "--server", "--electron" or "--library" flag') } if (target !== 'browser' && target !== 'server' && target !== 'electron' && target !== 'library') { throw new Error(util.format('Invalid target provided "%s", valid targets are "browser", "server", "electron" or "library"')) } return parseBuildTargetArgs(args, { cwdPath, resolveCwdPath, resolveSourcePath, resolveSourcePathRelative, resolveDestinationPath, mode, target, uglifyCode, runApplication, defaultDevServerPort, cleanArtifacts, sourcePath, runCorsProxy, defaultCorsProxyPort, destinationPath, nodeModulesPath, tsconfigPath, watchForChanges, parseEnvFiles, packageJson, appName, globalName, envConfig, processEnvConfig, generateSourceMaps, useCache, assetFilesExtensions, dynamicFilesInstructions, fuseboxCachePath, tailwindCssConfigPath, postCssConfigPath, postCssConfig, }) }) } function parseBuildTargetArgs (args, flags) { if (flags.target === 'browser') { // dynamic import for fusebox cache override let {parseBuildBrowserFlags} = require('./buildBrowser') return parseBuildBrowserFlags(args, flags) } else if (flags.target === 'server') { let {parseBuildServerFlags} = require('./buildServer') return parseBuildServerFlags(args, flags) } else if (flags.target === 'electron') { let {parseBuildElectronFlags} = require('./buildElectron') return parseBuildElectronFlags(args, flags) } else if (flags.target === 'library') { let {parseBuildLibraryFlags} = require('./buildLibrary') return parseBuildLibraryFlags(args, flags) } return flags } function buildCommand (flags) { let defaultCorsProxyPort = flags.map((f) => f.defaultCorsProxyPort).shift() || null return getFreePort(defaultCorsProxyPort) .then((corsProxyPort) => { let corsProxyHost = 'localhost' let runCorsProxy = flags.find((f) => f.runCorsProxy) if (runCorsProxy) { if (defaultCorsProxyPort !== corsProxyPort) { printHeader('CORS proxy port "%s" already in use, fallback to "%s"', defaultCorsProxyPort, corsProxyPort) } cors.createServer() .listen(corsProxyPort, corsProxyHost, () => { printHeader('CORS proxy server running on http://%s:%s', corsProxyHost, corsProxyPort) }) } if (flags.find((f) => f.cleanArtifacts)) { execSync(util.format('rm -rf %s', process.env.FUSEBOX_TEMP_FOLDER)) } let builds = flags.map((f) => { if (f.cleanArtifacts) { execSync(util.format('rm -rf %s', f.destinationPath)) } if (runCorsProxy) { f.envConfig = _.merge(f.envConfig, { CORS_PROXY_HOST: corsProxyHost, CORS_PROXY_PORT: corsProxyPort }) } printHeader('Loaded env config') console.dir(f.envConfig, {depth: 10}) if (f.target === 'browser') { // dynamic import, in order to override fuse box cache folder, see parseBrowserArgs let {buildBrowserCommand} = require('./buildBrowser') return buildBrowserCommand(f) } else if (f.target === 'server') { let {buildServerCommand} = require('./buildServer') return buildServerCommand(f) } else if (f.target === 'electron') { let {buildElectronCommand} = require('./buildElectron') return buildElectronCommand(f) } else if (f.target === 'library') { let {buildLibraryCommand} = require('./buildLibrary') return buildLibraryCommand(f) } }) return Promise.all(builds) }) }