@magic/core
Version:
@magic core. generate static pages and serverless lambdas. ~5kb client boilerplate.
347 lines (279 loc) • 9.05 kB
JavaScript
import child_process from 'child_process'
import path from 'path'
import cases from '@magic/cases'
import deep from '@magic/deep'
import error from '@magic/error'
import fs from '@magic/fs'
import is from '@magic/types'
import colors from './themes/colors.mjs'
import { findConfigFile, replaceSlashSlash, saveImport } from './lib/index.mjs'
const magicConfigNames = ['magic.mjs', 'magic.js']
const oldConfigName = 'config.mjs'
export const runConfig = async (args = {}) => {
let conf = {}
conf.CONFIG_FILE_PATH = await findConfigFile(
process.cwd(),
magicConfigNames,
oldConfigName,
args.silent,
)
if (!conf.CONFIG_FILE_PATH) {
return
}
const { default: imported } = await saveImport(conf.CONFIG_FILE_PATH)
conf = deep.merge(conf, imported)
// make sure conf.ROOT starts with or is equal to process.cwd()
if (!conf.ROOT) {
conf.ROOT = process.cwd()
} else if (!conf.ROOT.startsWith(process.cwd())) {
conf.ROOT = path.resolve(process.cwd(), conf.ROOT)
}
conf.NO_CHECK_LINKS = args.noCheckLinks || conf.NO_CHECK_LINKS
conf.NO_CHECK_LINKS_REMOTE = args.noCheckLinksRemote || conf.NO_CHECK_LINKS_REMOTE
conf.NO_CHECK_LINKS_EXIT = args.noCheckLinksExit || conf.NO_CHECK_LINKS_EXIT
// object to collect various directories in.
conf.DIR = conf.DIR || {}
// change to change the name of magic.js and magic.css
conf.CLIENT_LIB_NAME = conf.CLIENT_LIB_NAME || 'magic'
conf.NODE_MODULES = conf.NODE_MODULES || `${process.cwd()}${path.sep}node_modules`
// name of service-worker client file
conf.CLIENT_SERVICE_WORKER_NAME = conf.CLIENT_SERVICE_WORKER_NAME || 'service-worker'
// the host to serve this @magic app at
conf.HOST = conf.HOST || 'localhost'
// the port to serve this @magic app at
conf.PORT = conf.PORT || 2323
// the URL this app will be served at
conf.URL = conf.URL || false
// the CNAME of this app. domain.tld
conf.CNAME = conf.hasOwnProperty('CNAME') ? conf.CNAME : false
// set to false to not emit a robots.txt file
conf.ROBOTS_TXT = conf.hasOwnProperty('ROBOTS_TXT') ? conf.ROBOTS_TXT : true
// set to false to not emit a sitemap.xml file
conf.SITEMAP = conf.hasOwnProperty('SITEMAP') ? conf.SITEMAP : true
// the pages directory with page files to build
const PAGES = path.join(conf.ROOT, 'pages')
// the output dir that files get written to
const PUBLIC = path.join(process.cwd(), conf.PUBLIC || conf.DIR.PUBLIC || 'docs')
// assets dir, can include themes, modules, libraries
const ASSETS = path.join(conf.ROOT, 'assets')
const LIB = [path.join(ASSETS, 'lib'), path.join(conf.ROOT, 'lib')]
// module dir, modules get imported from here
const MODULES = path.join(ASSETS, 'modules')
// static directory, files in this dir get copied to conf.PUBLIC
let STATIC = path.join(ASSETS, 'static')
if (conf.ADD_STATIC) {
if (!is.array(conf.ADD_STATIC)) {
conf.ADD_STATIC = [conf.ADD_STATIC]
}
STATIC = [STATIC, ...conf.ADD_STATIC]
}
// themes dir, files in this dir get used as themes
const THEMES = path.join(ASSETS, 'themes')
// API dir for server side lambdas.
const API = path.join(process.cwd(), conf.API_DIR || 'api')
// global css variables that get used by @magic/css
const THEME_VARS = conf.THEME_VARS || {}
if (!THEME_VARS.colors) {
THEME_VARS.colors = colors
} else {
THEME_VARS.colors = {
...colors,
...THEME_VARS.colors,
}
}
// name of the file with all sri hashes
conf.HASH_FILE_NAME = conf.HASH_FILE_NAME || 'sri-hashes.json'
try {
const hashPath = path.join(PUBLIC, conf.HASH_FILE_NAME)
const content = await fs.readFile(hashPath, 'utf8')
conf.HASHES = JSON.parse(content)
} catch (e) {
if (e.code === 'ENOENT') {
conf.HASHES = {}
} else {
throw error(e)
}
}
// array of scripts that should be appended to the body
const mapScript = src => {
if (!src.startsWith(conf.WEB_ROOT)) {
src = replaceSlashSlash(`${conf.WEB_ROOT}/${src}`)
}
const result = {
src,
integrity: conf.HASHES[src],
}
if (!src.startsWith(conf.URL) && !src.startsWith('/')) {
result.crossorigin = 'anonymous'
}
return result
}
const scriptKeys = ['PREPEND_SCRIPTS', 'APPEND_SCRIPTS']
scriptKeys.forEach(key => {
// scripts that get added as script tags before of magic.js
if (is.empty(conf[key])) {
conf[key] = []
} else if (!is.array(conf.PREPEND_SCRIPTS)) {
conf[key] = [conf[key]]
}
conf[key] = conf[key].map(mapScript)
})
const addKeys = ['PREPEND_JS', 'APPEND_JS', 'PREPEND_CSS', 'APPEND_CSS']
addKeys.forEach(key => {
if (!conf[key]) {
conf[key] = []
}
if (!is.array(conf[key])) {
conf[key] = conf[key]
}
})
// array of html tags that get prepended before the #magic html tag
// structure: { name, props, children }
if (!conf.PREPEND_TAGS) {
conf.PREPEND_TAGS = []
} else if (!is.array(conf.PREPEND_TAGS)) {
conf.PREPEND_TAGS = [conf.PREPEND_TAGS]
}
// array of html tags that get appended after the #magic html tag
// structure: { name, props, children }
if (!conf.APPEND_TAGS) {
conf.APPEND_TAGS = []
} else if (!is.array(conf.APPEND_TAGS)) {
conf.APPEND_TAGS = [conf.APPEND_TAGS]
}
conf.INCLUDED_HASH_EXTENSIONS = conf.INCLUDED_HASH_EXTENSIONS || ['.txt', '.xml']
// the following files are zippable
const ZIPPABLE = [
'css',
'js',
'html',
'json',
'xml',
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'odt',
'csv',
'text',
'txt',
'ico',
]
conf.THEME = conf.THEME || []
// those are image formats.
const IMAGES = ['jpg', 'jpeg', 'png', 'svg', 'gif']
conf = deep.merge(conf, {
DIR: {
PAGES,
PUBLIC,
ASSETS,
MODULES,
STATIC,
THEMES,
API,
LIB,
},
FILETYPES: {
ZIPPABLE,
IMAGES,
},
IMAGEMIN: {
PNG: { quality: [0.95, 1] },
JPG: { quality: 95 },
GIF: { optimizationLevel: 3 },
SVGO: {
plugins: [
{
removeViewBox: false,
},
],
},
},
ENV: process.env.MAGIC_ENV || process.env.NODE_ENV || 'development',
})
// get environment settings for prod and dev
conf.IS_PROD = conf.ENV === 'production'
conf.IS_DEV = conf.ENV === 'development'
conf.IGNORED_STATIC = conf.IGNORED_STATIC || []
if (is.string(conf.IGNORED_STATIC)) {
conf.IGNORED_STATIC = [conf.IGNORED_STATIC]
}
conf.IGNORED_STATIC = conf.IGNORED_STATIC.map(st => {
if (!st.startsWith('.')) {
st = '.' + st
}
return st
})
// find WEB_ROOT manually from git.
// show warning if this has to be done, needs a few hundred ms
if (!conf.WEB_ROOT || !conf.URL) {
const startTime = new Date().getTime()
const stdout = child_process.execSync('git remote -v').toString()
let remote = stdout.split('\n')[1].split(/(\t| )/gim)[2]
let host
let org
let repo
if (remote.startsWith('git')) {
host = remote.split('@')[1].split('.')[0]
const [o, r] = remote.split(':')[1].split('/')
org = o
repo = r
} else if (remote.startsWith('http')) {
host = remote.split('://')[1].split('.')[0]
const [_, o, r] = remote.replace('://', '').split('/')
org = o
repo = r
if (repo === `${org}.${host}.io`) {
remote = `${org}.${host}`
} else {
remote = `${org}.${host}.io/${repo}`
}
}
if (repo === `${org}.${host}.io`) {
remote = `${org}.${host}.io`
} else {
remote = `${org}.${host}.io/${repo}`
}
if (!conf.WEB_ROOT) {
conf.WEB_ROOT = `/${repo}/`
}
conf.URL = remote
const timeSpent = new Date().getTime() - startTime
conf.URL_WARNING = timeSpent
}
// blog support
if (conf.BLOG_DIR) {
if (!conf.BLOG_DIR.startsWith(conf.ROOT)) {
conf.BLOG_DIR = path.join(conf.ROOT, conf.BLOG_DIR)
}
}
// the directory used to save temporary build files
conf.TMP_DIR = conf.TMP_DIR || '.tmp'
if (!conf.TMP_DIR.startsWith(process.cwd())) {
conf.TMP_DIR = path.join(process.cwd(), '.tmp')
}
// an array of @magic-modules that get appended to the body
conf.HOIST = conf.HOIST || []
// set to true to get babel build info
if (conf.BABEL) {
log.warn('W_DEPRECATED', 'config.BABEL is deprecated.')
}
// merge commandline arguments into config.
// --keep-client = KEEP_CLIENT
// --no-mangle-names = NO_MANGLE_NAMES
// --keep-console = KEEP_CONSOLE
// --keep-dead-code = KEEP_DEAD_CODE
// --keep-debugger = KEEP_DEBUGGER
// --no-check-links = NO_CHECK_LINKS
// --no-check-links-remote = NO_CHECK_LINKS_REMOTE
// --no-check-links-exit = NO_CHECK_LINKS_EXIT
Object.entries(args).map(([k, v]) => {
const snaked = cases.snakeCaps(k)
conf[snaked] = v || v === ''
})
conf.__DEPRECATED__ = [...scriptKeys, ...addKeys].filter(k => conf[k] && conf[k].length)
return conf
}